Conflicts: lib/api/v3/work_packages/work_package_representer.rbpull/1634/head
commit
1c6b684594
@ -0,0 +1,10 @@ |
||||
stage: |
||||
before: |
||||
- bower install --allow-root |
||||
- cp config/configuration.yml.example config/configuration.yml |
||||
- cp config/database.teatro.yml config/database.yml |
||||
- export SECRET_TOKEN=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa |
||||
|
||||
database: |
||||
- bundle exec rake db:create db:migrate |
||||
- bundle exec rake db:seed RAILS_ENV=development |
@ -0,0 +1,155 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
angular.module('openproject.workPackages.controllers') |
||||
|
||||
.constant('DEFAULT_WORK_PACKAGE_PROPERTIES', [ |
||||
'status', 'assignee', 'responsible', |
||||
'date', 'percentageDone', 'priority', |
||||
'estimatedTime', 'versionName' |
||||
]) |
||||
.constant('USER_TYPE', 'user') |
||||
|
||||
.controller('DetailsTabOverviewController', [ |
||||
'$scope', |
||||
'I18n', |
||||
'DEFAULT_WORK_PACKAGE_PROPERTIES', |
||||
'USER_TYPE', |
||||
'CustomFieldHelper', |
||||
'WorkPackagesHelper', |
||||
'UserService', |
||||
'$q', |
||||
function($scope, I18n, DEFAULT_WORK_PACKAGE_PROPERTIES, USER_TYPE, CustomFieldHelper, WorkPackagesHelper, UserService, $q) { |
||||
|
||||
// work package properties
|
||||
|
||||
$scope.presentWorkPackageProperties = []; |
||||
$scope.emptyWorkPackageProperties = []; |
||||
$scope.userPath = PathHelper.staticUserPath; |
||||
|
||||
var workPackageProperties = DEFAULT_WORK_PACKAGE_PROPERTIES; |
||||
|
||||
function getPropertyValue(property, format) { |
||||
if (format === USER_TYPE) { |
||||
return $scope.workPackage.embedded[property]; |
||||
} else { |
||||
return getFormattedPropertyValue(property); |
||||
} |
||||
} |
||||
|
||||
function getFormattedPropertyValue(property) { |
||||
if (property === 'date') { |
||||
return getDateProperty(); |
||||
} else { |
||||
return WorkPackagesHelper.formatWorkPackageProperty($scope.workPackage.props[property], property); |
||||
} |
||||
} |
||||
|
||||
function getDateProperty() { |
||||
if ($scope.workPackage.props.startDate || $scope.workPackage.props.dueDate) { |
||||
var displayedStartDate = WorkPackagesHelper.formatWorkPackageProperty($scope.workPackage.props.startDate, 'startDate') || I18n.t('js.label_no_start_date'), |
||||
displayedEndDate = WorkPackagesHelper.formatWorkPackageProperty($scope.workPackage.props.dueDate, 'dueDate') || I18n.t('js.label_no_due_date'); |
||||
|
||||
return displayedStartDate + ' - ' + displayedEndDate; |
||||
} |
||||
} |
||||
|
||||
function addFormattedValueToPresentProperties(property, label, value, format) { |
||||
var propertyData = { |
||||
property: property, |
||||
label: label, |
||||
format: format, |
||||
value: null |
||||
}; |
||||
$q.when(value).then(function(value) { |
||||
propertyData.value = value; |
||||
}); |
||||
$scope.presentWorkPackageProperties.push(propertyData); |
||||
} |
||||
|
||||
function secondRowToBeDisplayed() { |
||||
return !!workPackageProperties |
||||
.slice(3, 6) |
||||
.map(function(property) { |
||||
return $scope.workPackage.props[property]; |
||||
}) |
||||
.reduce(function(a, b) { |
||||
return a || b; |
||||
}); |
||||
} |
||||
|
||||
var userFields = ['assignee', 'author', 'responsible']; |
||||
|
||||
(function setupWorkPackageProperties() { |
||||
angular.forEach(workPackageProperties, function(property, index) { |
||||
var label = I18n.t('js.work_packages.properties.' + property), |
||||
format = userFields.indexOf(property) === -1 ? 'text' : USER_TYPE, |
||||
value = getPropertyValue(property, format); |
||||
|
||||
if (!!value || |
||||
index < 3 || |
||||
index < 6 && secondRowToBeDisplayed()) { |
||||
addFormattedValueToPresentProperties(property, label, value, format); |
||||
} else { |
||||
$scope.emptyWorkPackageProperties.push(label); |
||||
} |
||||
}); |
||||
})(); |
||||
|
||||
function getCustomPropertyValue(customProperty) { |
||||
if (!!customProperty.value && customProperty.format === USER_TYPE) { |
||||
return UserService.getUser(customProperty.value); |
||||
} else { |
||||
return CustomFieldHelper.formatCustomFieldValue(customProperty.value, customProperty.format); |
||||
} |
||||
} |
||||
|
||||
(function setupCustomProperties() { |
||||
angular.forEach($scope.workPackage.props.customProperties, function(customProperty) { |
||||
var property = customProperty.name, |
||||
label = customProperty.name, |
||||
value = getCustomPropertyValue(customProperty), |
||||
format = customProperty.format; |
||||
|
||||
if (customProperty.value) { |
||||
addFormattedValueToPresentProperties(property, label, value, format); |
||||
} else { |
||||
$scope.emptyWorkPackageProperties.push(label); |
||||
} |
||||
}); |
||||
})(); |
||||
|
||||
// toggles
|
||||
|
||||
$scope.toggleStates = { |
||||
hideFullDescription: true, |
||||
hideAllAttributes: true |
||||
}; |
||||
|
||||
|
||||
}]); |
@ -0,0 +1,92 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
angular.module('openproject.workPackages.controllers') |
||||
|
||||
.controller('DetailsTabWatchersController', ['$scope', 'workPackage', function($scope, workPackage) { |
||||
// available watchers
|
||||
|
||||
$scope.$watch('watchers.length', fetchAvailableWatchers); fetchAvailableWatchers(); |
||||
|
||||
/** |
||||
* @name getResourceIdentifier |
||||
* @function |
||||
* |
||||
* @description |
||||
* Returns the resource identifier of an API resource retrieved via hyperagent |
||||
* |
||||
* @param {Object} resource The resource object |
||||
* |
||||
* @returns {String} identifier |
||||
*/ |
||||
function getResourceIdentifier(resource) { |
||||
// TODO move to helper
|
||||
return resource.links.self.href; |
||||
} |
||||
|
||||
/** |
||||
* @name getFilteredCollection |
||||
* @function |
||||
* |
||||
* @description |
||||
* Filters collection of HAL resources by entries listed in resourcesToBeFilteredOut |
||||
* |
||||
* @param {Array} collection Array of resources retrieved via hyperagend |
||||
* @param {Array} resourcesToBeFilteredOut Entries to be filtered out |
||||
* |
||||
* @returns {Array} filtered collection |
||||
*/ |
||||
function getFilteredCollection(collection, resourcesToBeFilteredOut) { |
||||
return collection.filter(function(resource) { |
||||
return resourcesToBeFilteredOut.map(getResourceIdentifier).indexOf(getResourceIdentifier(resource)) === -1; |
||||
}); |
||||
} |
||||
|
||||
function fetchAvailableWatchers() { |
||||
$scope.workPackage.links.availableWatchers |
||||
.fetch() |
||||
.then(function(data) { |
||||
// Temporarily filter out watchers already assigned to the work package on the client-side
|
||||
$scope.availableWatchers = getFilteredCollection(data.embedded.availableWatchers, $scope.watchers); |
||||
// TODO do filtering on the API side and replace the update of the available watchers with the code provided in the following line
|
||||
// $scope.availableWatchers = data.embedded.availableWatchers;
|
||||
}); |
||||
} |
||||
|
||||
$scope.addWatcher = function(id) { |
||||
$scope.workPackage.link('addWatcher', {user_id: id}) |
||||
.fetch({ajax: {method: 'POST'}}) |
||||
.then($scope.refreshWorkPackage, $scope.outputError); |
||||
}; |
||||
|
||||
$scope.deleteWatcher = function(watcher) { |
||||
watcher.links.removeWatcher |
||||
.fetch({ ajax: watcher.links.removeWatcher.props }) |
||||
.then($scope.refreshWorkPackage, $scope.outputError); |
||||
}; |
||||
}]); |
@ -0,0 +1,48 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
angular.module('openproject.workPackages.directives') |
||||
|
||||
.directive('attachmentFileSize', [function(){ |
||||
return { |
||||
restrict: 'A', |
||||
replace: false, |
||||
templateUrl: '/templates/work_packages/tabs/_attachment_file_size.html', |
||||
scope: { |
||||
attachment: '=' |
||||
}, |
||||
link: function(scope, element, attributes) { |
||||
scope.displayFileSize = "(" + formattedFileSize(scope.attachment.props.fileSize) + ")"; |
||||
|
||||
function formattedFileSize(fileSize) { |
||||
var size = parseFloat(fileSize); |
||||
return isNaN(size) ? "0kB" : (size / 1000).toFixed(2) + "kB"; |
||||
}; |
||||
} |
||||
}; |
||||
}]); |
@ -0,0 +1,81 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
// TODO move to UI components
|
||||
angular.module('openproject.uiComponents') |
||||
|
||||
.directive('workPackageRelation', [ |
||||
'I18n', |
||||
'PathHelper', |
||||
'WorkPackagesHelper', |
||||
function(I18n, PathHelper, WorkPackagesHelper) { |
||||
return { |
||||
restrict: 'E', |
||||
replace: true, |
||||
scope: { title: '@', relatedWorkPackages: '=', btnTitle: '@buttonTitle', btnIcon: '@buttonIcon', isSingletonRelation: '@singletonRelation' }, |
||||
templateUrl: '/templates/work_packages/tabs/_work_package_relation.html', |
||||
link: function(scope, element, attrs) { |
||||
scope.I18n = I18n; |
||||
scope.WorkPackagesHelper = WorkPackagesHelper; |
||||
scope.workPackagePath = PathHelper.staticWorkPackagePath; |
||||
scope.userPath = PathHelper.staticUserPath; |
||||
|
||||
var setExpandState = function() { |
||||
scope.expand = scope.relatedWorkPackages && scope.relatedWorkPackages.length > 0; |
||||
}; |
||||
|
||||
scope.$watch('relatedWorkPackages', function() { |
||||
setExpandState(); |
||||
}); |
||||
|
||||
scope.collapseStateIcon = function(collapsed) { |
||||
var iconClass = 'icon-arrow-right5-'; |
||||
|
||||
if (collapsed) { |
||||
iconClass += '3'; |
||||
} else { |
||||
iconClass += '2'; |
||||
} |
||||
|
||||
return iconClass; |
||||
} |
||||
|
||||
scope.getFullIdentifier = function(workPackage) { |
||||
var id = '#' + workPackage.props.id; |
||||
|
||||
if (workPackage.props.type) { |
||||
id += ' ' + workPackage.props.type + ':'; |
||||
} |
||||
|
||||
id += ' ' + workPackage.props.subject; |
||||
|
||||
return id; |
||||
}; |
||||
} |
||||
}; |
||||
}]); |
@ -0,0 +1,7 @@ |
||||
# Components - Add comments - default |
||||
|
||||
``` |
||||
<div class="activity-comment"> |
||||
<textarea class="add-comment-text" placeholder="Add your comments here" rows=1></textarea> |
||||
</div> |
||||
``` |
@ -0,0 +1,44 @@ |
||||
/*-- copyright |
||||
* OpenProject is a project management system. |
||||
* Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License version 3. |
||||
* |
||||
* OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
* Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
* Copyright (C) 2010-2013 the ChiliProject Team |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
* |
||||
* See doc/COPYRIGHT.rdoc for more details. ++*/ |
||||
|
||||
.activity-comment |
||||
textarea |
||||
border: 1px solid #cacaca |
||||
background: #ffffff |
||||
border-radius: 2px |
||||
padding: 8px |
||||
font-family: $font_family_normal |
||||
font-size: $global_font_size |
||||
width: 100% |
||||
box-sizing: border-box |
||||
&:hover |
||||
border: 1px solid #aaaaaa |
||||
&:focus |
||||
border: 1px solid #aaaaaa |
||||
box-shadow: 1px 1px 1px #dddddd inset |
||||
.add-comment-text |
||||
resize: none |
@ -0,0 +1,10 @@ |
||||
# Components - Add comments - onclick |
||||
|
||||
``` |
||||
<div class="activity-comment"> |
||||
<textarea class="add-comment-text-big" placeholder="Add your comments here" rows=4></textarea> |
||||
<button class="button"><i class="icon-yes icon-left"></i>Add comment</button><button class="button"><i class="icon-close icon-left"></i>Cancel</button> |
||||
</div> |
||||
|
||||
``` |
||||
|
@ -0,0 +1,28 @@ |
||||
/*-- copyright |
||||
* OpenProject is a project management system. |
||||
* Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License version 3. |
||||
* |
||||
* OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
* Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
* Copyright (C) 2010-2013 the ChiliProject Team |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
* |
||||
* See doc/COPYRIGHT.rdoc for more details. ++*/ |
||||
|
||||
|
@ -0,0 +1,87 @@ |
||||
/*-- copyright |
||||
* OpenProject is a project management system. |
||||
* Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License version 3. |
||||
* |
||||
* OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
* Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
* Copyright (C) 2010-2013 the ChiliProject Team |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
* |
||||
* See doc/COPYRIGHT.rdoc for more details. ++ |
||||
*/ |
||||
|
||||
.attachments-container |
||||
float: left |
||||
margin: 0 0 30px 0 |
||||
width: 100% |
||||
ul |
||||
margin: 0 |
||||
padding: 0 |
||||
list-style-type: none |
||||
li |
||||
margin: 0 |
||||
padding: 0 |
||||
line-height: 20px |
||||
table |
||||
padding: 0 |
||||
margin: 0px 0 10px 0 |
||||
float: left |
||||
border-collapse: collapse |
||||
border: 0px solid #ddd |
||||
width: 100% |
||||
table-layout: fixed |
||||
tbody |
||||
tr |
||||
td |
||||
white-space: nowrap |
||||
overflow: hidden |
||||
text-overflow: ellipsis |
||||
width: 10% |
||||
tr |
||||
&:hover |
||||
background: #ffffae |
||||
th |
||||
text-align: left |
||||
font-family: 'LatoBold' |
||||
font-weight: normal |
||||
text-transform: uppercase |
||||
background: #fff |
||||
padding: 6px 10px 6px 0 |
||||
border-bottom: 2px solid #eee |
||||
td |
||||
text-align: left |
||||
font-weight: normal |
||||
border-bottom: 0px solid #ddd |
||||
padding: 6px 10px 6px 0 |
||||
|
||||
|
||||
.add-file |
||||
float: left |
||||
padding: 8px 0 0 10px |
||||
i |
||||
font-size: 12px |
||||
padding: 0 2px 0 0 |
||||
|
||||
.upload-file |
||||
display: block |
||||
width: 100% |
||||
float: left |
||||
margin: 20px 0 0 0 |
||||
padding: 20px 0 0 0 |
||||
border-top: 1px solid #ddd |
@ -0,0 +1,52 @@ |
||||
/*-- copyright |
||||
* OpenProject is a project management system. |
||||
* Copyright (C) 2012-2013 the OpenProject Foundation (OPF) |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License version 3. |
||||
* |
||||
* OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
* Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
* Copyright (C) 2010-2013 the ChiliProject Team |
||||
* |
||||
* This program is free software; you can redistribute it and/or |
||||
* modify it under the terms of the GNU General Public License |
||||
* as published by the Free Software Foundation; either version 2 |
||||
* of the License, or (at your option) any later version. |
||||
* |
||||
* This program is distributed in the hope that it will be useful, |
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
* GNU General Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License |
||||
* along with this program; if not, write to the Free Software |
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
* |
||||
* See doc/COPYRIGHT.rdoc for more details. ++ |
||||
*/ |
||||
|
||||
.detail-panel-description |
||||
width: 100% |
||||
.detail-panel-description-content |
||||
.relation |
||||
h3 |
||||
cursor: pointer |
||||
.content |
||||
.workpackages |
||||
table |
||||
width: 100% |
||||
table-layout: fixed |
||||
thead |
||||
font-weight: bold |
||||
text-transform: uppercase |
||||
line-height: 32px |
||||
tr |
||||
td |
||||
text-overflow: ellipsis |
||||
white-space: nowrap |
||||
overflow: hidden |
||||
&:first-of-type |
||||
width: 55% |
||||
&:last-of-type |
||||
width: 30% |
@ -0,0 +1,32 @@ |
||||
<%#-- copyright |
||||
OpenProject is a project management system. |
||||
Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License version 3. |
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
Copyright (C) 2010-2013 the ChiliProject Team |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License |
||||
as published by the Free Software Foundation; either version 2 |
||||
of the License, or (at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program; if not, write to the Free Software |
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
See doc/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
|
||||
<div id="nav-login-content"> |
||||
<%= render :partial => 'account/auth_providers' %> |
||||
</div> |
@ -0,0 +1,63 @@ |
||||
<%#-- copyright |
||||
OpenProject is a project management system. |
||||
Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License version 3. |
||||
|
||||
OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
Copyright (C) 2010-2013 the ChiliProject Team |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License |
||||
as published by the Free Software Foundation; either version 2 |
||||
of the License, or (at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program; if not, write to the Free Software |
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
See doc/COPYRIGHT.rdoc for more details. |
||||
|
||||
++#%> |
||||
|
||||
<%= form_tag({:action=> "login"}, autocomplete: 'off') do %> |
||||
<%= back_url_hidden_field_tag %> |
||||
|
||||
<div class="attribute_wrapper"> |
||||
<label for="username"><%= User.human_attribute_name :login %></label> |
||||
<%= text_field_tag 'username', nil %> |
||||
</div> |
||||
|
||||
<div class="attribute_wrapper"> |
||||
<label for="password"><%= User.human_attribute_name :password %></label> |
||||
<%= password_field_tag 'password', nil %> |
||||
</div> |
||||
|
||||
<div class="login-options-container"> |
||||
<div class="login-links"> |
||||
<% if Setting.lost_password? %> |
||||
<%= link_to l(:label_password_lost), :controller => '/account', :action => 'lost_password' %> |
||||
<br> |
||||
<% end %> |
||||
<% if Setting.self_registration? %> |
||||
<%= link_to l(:label_register), { :controller => '/account', :action => 'register' } %> |
||||
<% end %> |
||||
</div> |
||||
<% if Setting.autologin? %> |
||||
<div class="attribute_wrapper indented"> |
||||
<label for="autologin"> |
||||
<%= check_box_tag 'autologin', 1, false %> <%= l(:label_stay_logged_in) %> |
||||
</label> |
||||
</div> |
||||
<% end %> |
||||
</div> |
||||
<input type="submit" name="login" value="<%=l(:button_login)%>" class="button_highlight" /> |
||||
<%= javascript_tag "Form.Element.focus('username');" %> |
||||
<% end %> |
@ -0,0 +1,13 @@ |
||||
default: &default |
||||
adapter: postgresql |
||||
encoding: unicode |
||||
database: teatro |
||||
pool: 5 |
||||
min_messages: warning |
||||
username: postgres |
||||
|
||||
development: |
||||
<<: *default |
||||
|
||||
production: |
||||
<<: *default |
@ -0,0 +1,116 @@ |
||||
# Subversion and Git Integration |
||||
|
||||
OpenProject can (by default) browse subversion and git repositories. |
||||
But it does not serves them to git/svn clients. |
||||
|
||||
However, with the help of the apache webserver it is possible to serve repositories. |
||||
|
||||
## Set-up |
||||
|
||||
OpenProject should run integrated in your apache setup. This can be done in several ways |
||||
(for example by using the passenger module). |
||||
In this document we assume that you run OpenProject using a separate process, which listens |
||||
for requests on http://localhost:3000. |
||||
|
||||
We let apache serve svn and git repositories (with the help of some modules) and |
||||
authenticate against the OpenProject user database. |
||||
Therefore we use an authentication perl script located in extra/svn/OpenProjectAuthentication.pm . |
||||
|
||||
It requires some apache modules to be enabled and installed: |
||||
|
||||
<pre> |
||||
aptitude install libapache2-mod-perl2 libapache2-svn |
||||
a2enmod proxy proxy_http |
||||
</pre> |
||||
|
||||
Also, the extra/svn/OpenProjectAuthentication.pm script needs to be in your apache perl path |
||||
(for example it might be sym-linked into /etc/apache2/Apache). |
||||
|
||||
To make the authentication work, you need to generate a secret repository API key. To do this, open |
||||
your OpenProject installation in your favourite web browser, log in as an administrator and go to |
||||
Modules -> Administration -> Settings -> Repositories. |
||||
On that page, enable the "Enable WS for repository management" setting and generate an API key (do not |
||||
for get to save the settings). We need that API key later in our apache config. |
||||
|
||||
Find a place to store the repositories. For this guide we assume that you put your svn repositories in |
||||
/srv/openproject/svn . All things in that repository should be accessible by the apache system user and |
||||
by the user running your openproject server. |
||||
|
||||
## An example apache configuration |
||||
|
||||
We provide an example apache configuration. Some details are explained inline as comments. |
||||
|
||||
<pre> |
||||
# Load OpenProject per module used to authenticate requests against the user database. |
||||
# Be sure that the OpenProjectAuthentication.pm script in located in your perl path. |
||||
PerlSwitches -I/srv/www/perl-lib -T |
||||
PerlLoadModule Apache::OpenProjectAuthentication |
||||
|
||||
<VirtualHost *:80> |
||||
ErrorLog /var/log/apache2/error |
||||
|
||||
# The /sys endpoint is an internal API used to authenticate repository |
||||
# access requests. It shall not be reachable from remote. |
||||
<LocationMatch "/sys"> |
||||
Order Deny,Allow |
||||
Deny from all |
||||
Allow from 127.0.0.1 |
||||
</LocationMatch> |
||||
|
||||
# This fixes COPY for webdav over https |
||||
RequestHeader edit Destination ^https: http: early |
||||
|
||||
# Serves svn repositories locates in /srv/openproject/svn via WebDAV |
||||
# It is secure with basic auth against the OpenProject user database. |
||||
<Location /svn> |
||||
DAV svn |
||||
SVNParentPath "/srv/openproject/svn" |
||||
DirectorySlash Off |
||||
|
||||
AuthType Basic |
||||
AuthName "Secured Area" |
||||
Require valid-user |
||||
|
||||
PerlAccessHandler Apache::Authn::OpenProject::access_handler |
||||
PerlAuthenHandler Apache::Authn::OpenProject::authen_handler |
||||
|
||||
OpenProjectUrl 'http://127.0.0.1:3000' |
||||
OpenProjectApiKey 'REPLACE WITH REPOSITORY API KEY' |
||||
|
||||
<Limit OPTIONS PROPFIND GET REPORT MKACTIVITY PROPPATCH PUT CHECKOUT MKCOL MOVE COPY DELETE LOCK UNLOCK MERGE> |
||||
Allow from all |
||||
</Limit> |
||||
</Location> |
||||
|
||||
# Requires the apache module mod_proxy. Enable it with |
||||
# a2enmod proxy proxy_http |
||||
# See: http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#ProxyPass |
||||
# Note that the ProxyPass with the longest path should be listed first, otherwise |
||||
# a shorter path may match and will do an early redirect (without looking for other |
||||
# more specific matching paths). |
||||
ProxyPass /svn ! |
||||
ProxyPass / http://127.0.0.1:3000/ |
||||
ProxyPassReverse / http://127.0.0.1:3000/ |
||||
</VirtualHost> |
||||
</pre> |
||||
|
||||
## Automatically create repositories with reposman.rb |
||||
|
||||
The reposman.rb script can create repositories for your newly created OpenProject projects. |
||||
It is useful when run from a cron job (so that repositories appear 'magically' some time after you created |
||||
a project in the OpenProject administration view). |
||||
|
||||
<pre> |
||||
ruby extra/svn/reposman.rb \ |
||||
--openproject-host "http://127.0.0.1:3000" \ |
||||
--owner "www-data" \ |
||||
--group "openproject" \ |
||||
--public-mode '2750' \ |
||||
--private-mode '2750' \ |
||||
--svn-dir "/srv/openproject/svn" \ |
||||
--url "file:///srv/openproject/svn" \ |
||||
--key "REPLACE WITH REPOSITORY API KEY" \ |
||||
--scm Subversion \ |
||||
--verbose |
||||
</pre> |
||||
|
@ -0,0 +1,295 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
/*jshint expr: true*/ |
||||
|
||||
describe('DetailsTabOverviewController', function() { |
||||
var scope; |
||||
var buildController; |
||||
var I18n = { t: angular.identity }, |
||||
WorkPackagesHelper = { |
||||
formatWorkPackageProperty: angular.identity |
||||
}, |
||||
UserService = { |
||||
getUser: angular.identity |
||||
}, |
||||
CustomFieldHelper = { |
||||
formatCustomFieldValue: angular.identity |
||||
}, |
||||
workPackage = { |
||||
props: { |
||||
status: 'open', |
||||
versionName: null, |
||||
customProperties: [ |
||||
{ format: 'text', name: 'color', value: 'red' }, |
||||
] |
||||
}, |
||||
embedded: { |
||||
activities: [], |
||||
watchers: [], |
||||
attachments: [] |
||||
}, |
||||
}; |
||||
|
||||
function buildWorkPackageWithId(id) { |
||||
angular.extend(workPackage.props, {id: id}); |
||||
return workPackage; |
||||
} |
||||
|
||||
beforeEach(module('openproject.api', 'openproject.services', 'openproject.workPackages.controllers')); |
||||
beforeEach(inject(function($rootScope, $controller, $timeout) { |
||||
var workPackageId = 99; |
||||
|
||||
buildController = function() { |
||||
scope = $rootScope.$new(); |
||||
scope.workPackage = workPackage; |
||||
|
||||
ctrl = $controller("DetailsTabOverviewController", { |
||||
$scope: scope, |
||||
I18n: I18n, |
||||
UserService: UserService, |
||||
CustomFieldHelper: CustomFieldHelper, |
||||
}); |
||||
|
||||
$timeout.flush(); |
||||
}; |
||||
|
||||
})); |
||||
|
||||
describe('initialisation', function() { |
||||
it('should initialise', function() { |
||||
buildController(); |
||||
}); |
||||
}); |
||||
|
||||
describe('work package properties', function() { |
||||
function fetchPresentPropertiesWithName(propertyName) { |
||||
return scope.presentWorkPackageProperties.filter(function(propertyData) { |
||||
return propertyData.property === propertyName; |
||||
}); |
||||
} |
||||
|
||||
describe('when the property has a value', function() { |
||||
var propertyName = 'status'; |
||||
|
||||
beforeEach(function() { |
||||
buildController(); |
||||
}); |
||||
|
||||
it('adds properties to present properties', function() { |
||||
expect(fetchPresentPropertiesWithName(propertyName)).to.have.length(1); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the property is among the first 3 properties', function() { |
||||
var propertyName = 'responsible'; |
||||
|
||||
beforeEach(function() { |
||||
buildController(); |
||||
}); |
||||
|
||||
it('is added to present properties even if it is empty', function() { |
||||
expect(fetchPresentPropertiesWithName(propertyName)).to.have.length(1); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the property is among the second group of 3 properties', function() { |
||||
var propertyName = 'priority', |
||||
label = 'Priority'; |
||||
|
||||
beforeEach(function() { |
||||
sinon.stub(I18n, 't') |
||||
.withArgs('js.work_packages.properties.' + propertyName) |
||||
.returns(label); |
||||
|
||||
buildController(); |
||||
}); |
||||
|
||||
afterEach(function() { |
||||
I18n.t.restore(); |
||||
}); |
||||
|
||||
describe('and none of these 3 properties is present', function() { |
||||
beforeEach(function() { |
||||
buildController(); |
||||
}); |
||||
|
||||
it('is added to the empty properties', function() { |
||||
expect(scope.emptyWorkPackageProperties.indexOf(label)).to.be.greaterThan(-1); |
||||
}); |
||||
}); |
||||
|
||||
describe('and at least one of these 3 properties is present', function() { |
||||
beforeEach(function() { |
||||
workPackage.props.percentageDone = '20'; |
||||
buildController(); |
||||
}); |
||||
|
||||
it('is added to the present properties', function() { |
||||
expect(fetchPresentPropertiesWithName(propertyName)).to.have.length(1); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when the property is not among the first 6 properties', function() { |
||||
var propertyName = 'versionName', |
||||
label = 'Version'; |
||||
|
||||
beforeEach(function() { |
||||
sinon.stub(I18n, 't') |
||||
.withArgs('js.work_packages.properties.' + propertyName) |
||||
.returns(label); |
||||
|
||||
buildController(); |
||||
}); |
||||
|
||||
afterEach(function() { |
||||
I18n.t.restore(); |
||||
}); |
||||
|
||||
it('adds properties that without values to empty properties', function() { |
||||
expect(scope.emptyWorkPackageProperties.indexOf(label)).to.be.greaterThan(-1); |
||||
}); |
||||
}); |
||||
|
||||
describe('date property', function() { |
||||
var startDate = '2014-07-09', |
||||
dueDate = '2014-07-10', |
||||
placeholder = 'placeholder'; |
||||
|
||||
|
||||
describe('when only the due date is present', function() { |
||||
beforeEach(function() { |
||||
sinon.stub(I18n, 't') |
||||
.withArgs('js.label_no_start_date') |
||||
.returns(placeholder); |
||||
|
||||
workPackage.props.startDate = null; |
||||
workPackage.props.dueDate = dueDate; |
||||
|
||||
buildController(); |
||||
}); |
||||
|
||||
afterEach(function() { |
||||
I18n.t.restore(); |
||||
}); |
||||
|
||||
it('renders the due date and a placeholder for the start date as date property', function() { |
||||
expect(fetchPresentPropertiesWithName('date')[0].value).to.equal(placeholder + ' - Jul 10, 2014'); |
||||
}); |
||||
}); |
||||
|
||||
describe('when only the start date is present', function() { |
||||
beforeEach(function() { |
||||
sinon.stub(I18n, 't') |
||||
.withArgs('js.label_no_due_date') |
||||
.returns(placeholder); |
||||
|
||||
workPackage.props.startDate = startDate; |
||||
workPackage.props.dueDate = null; |
||||
|
||||
buildController(); |
||||
}); |
||||
|
||||
afterEach(function() { |
||||
I18n.t.restore(); |
||||
}); |
||||
|
||||
it('renders the start date and a placeholder for the due date as date property', function() { |
||||
expect(fetchPresentPropertiesWithName('date')[0].value).to.equal('Jul 9, 2014 - ' + placeholder); |
||||
}); |
||||
}); |
||||
|
||||
describe('when both - start and due date are present', function() { |
||||
beforeEach(function() { |
||||
workPackage.props.startDate = startDate; |
||||
workPackage.props.dueDate = dueDate; |
||||
|
||||
buildController(); |
||||
}); |
||||
|
||||
it('combines them and renders them as date property', function() { |
||||
expect(fetchPresentPropertiesWithName('date')[0].value).to.equal('Jul 9, 2014 - Jul 10, 2014'); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('custom field properties', function() { |
||||
var customPropertyName = 'color'; |
||||
|
||||
describe('when the property has a value', function() { |
||||
beforeEach(function() { |
||||
formatCustomFieldValueSpy = sinon.spy(CustomFieldHelper, 'formatCustomFieldValue'); |
||||
|
||||
buildController(); |
||||
}); |
||||
|
||||
afterEach(function() { |
||||
CustomFieldHelper.formatCustomFieldValue.restore(); |
||||
}); |
||||
|
||||
it('adds properties to present properties', function() { |
||||
expect(fetchPresentPropertiesWithName(customPropertyName)).to.have.length(1); |
||||
}); |
||||
|
||||
it('formats values using the custom field helper', function() { |
||||
expect(CustomFieldHelper.formatCustomFieldValue.calledWith('red', 'text')).to.be.true; |
||||
}); |
||||
}); |
||||
|
||||
describe('when the property does not have a value', function() { |
||||
beforeEach(function() { |
||||
workPackage.props.customProperties[0].value = null; |
||||
buildController(); |
||||
}); |
||||
|
||||
it('adds the custom property to empty properties', function() { |
||||
expect(scope.emptyWorkPackageProperties.indexOf(customPropertyName)).to.be.greaterThan(-1); |
||||
}); |
||||
}); |
||||
|
||||
describe('user custom property', function() { |
||||
var userId = '1'; |
||||
|
||||
beforeEach(function() { |
||||
workPackage.props.customProperties[0].value = userId; |
||||
workPackage.props.customProperties[0].format = 'user'; |
||||
|
||||
getUserSpy = sinon.spy(UserService, 'getUser'); |
||||
buildController(); |
||||
}); |
||||
|
||||
it('fetches the user using the user service', function() { |
||||
expect(UserService.getUser.calledWith(userId)).to.be.true; |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
|
||||
}); |
@ -0,0 +1,234 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
describe('Work Package Relation Directive', function() { |
||||
var I18n, PathHelper, compile, element, scope; |
||||
|
||||
beforeEach(angular.mock.module('openproject.uiComponents', 'openproject.helpers', 'ngSanitize')); |
||||
beforeEach(module('templates', function($provide) { |
||||
})); |
||||
|
||||
beforeEach(inject(function($rootScope, $compile, _I18n_, _PathHelper_) { |
||||
scope = $rootScope.$new(); |
||||
|
||||
compile = function(html) { |
||||
element = $compile(html)(scope); |
||||
scope.$digest(); |
||||
}; |
||||
|
||||
I18n = _I18n_; |
||||
PathHelper = _PathHelper_; |
||||
|
||||
var stub = sinon.stub(I18n, 't'); |
||||
|
||||
stub.withArgs('js.work_packages.properties.subject').returns('Column0'); |
||||
stub.withArgs('js.work_packages.properties.status').returns('Column1'); |
||||
stub.withArgs('js.work_packages.properties.assignee').returns('Column2'); |
||||
})); |
||||
|
||||
afterEach(function() { |
||||
I18n.t.restore(); |
||||
}); |
||||
|
||||
var multiElementHtml = "<work-package-relation title='MyRelation' related-work-packages='relations' button-title='Add Relation' button-icon='%MyIcon%'></work-package-relation>" |
||||
var singleElementHtml = "<work-package-relation title='MyRelation' related-work-packages='relations' button-title='Add Relation' button-icon='%MyIcon%' singleton-relation='true'></work-package-relation>" |
||||
|
||||
|
||||
var workPackage1; |
||||
var workPackage2; |
||||
|
||||
beforeEach(function() { |
||||
workPackage1 = { |
||||
props: { |
||||
id: "1", |
||||
subject: "Subject 1", |
||||
status: "Status 1" |
||||
}, |
||||
embedded: { |
||||
assignee: { |
||||
props: { |
||||
name: "Assignee 1", |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
workPackage2 = { |
||||
props: { |
||||
id: "2", |
||||
subject: "Subject 2", |
||||
status: "Status 2" |
||||
}, |
||||
embedded: { |
||||
assignee: { |
||||
props: { |
||||
name: "Assignee 2", |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
}); |
||||
|
||||
var shouldBehaveLikeRelationDirective = function() { |
||||
it('should have a title', function() { |
||||
var title = angular.element(element.find('h3')); |
||||
|
||||
expect(title.text()).to.include('MyRelation'); |
||||
}); |
||||
|
||||
//it('should have a button', function() {
|
||||
// var button = angular.element(element.find('button.button'));
|
||||
|
||||
// expect(button.attr('title')).to.include('Add Relation');
|
||||
// expect(button.text()).to.include('Add Relation');
|
||||
// expect(button.text()).to.include('%MyIcon%');
|
||||
//});
|
||||
}; |
||||
|
||||
var shouldBehaveLikeHasTableHeader = function() { |
||||
it('should have a table head', function() { |
||||
var column0 = angular.element(element.find('.workpackages table thead td:nth-child(1)')); |
||||
var column1 = angular.element(element.find('.workpackages table thead td:nth-child(2)')); |
||||
var column2 = angular.element(element.find('.workpackages table thead td:nth-child(3)')); |
||||
|
||||
expect(angular.element(column0).text()).to.eq(I18n.t('js.work_packages.properties.subject')); |
||||
expect(angular.element(column1).text()).to.eq(I18n.t('js.work_packages.properties.status')); |
||||
expect(angular.element(column2).text()).to.eq(I18n.t('js.work_packages.properties.assignee')); |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeHasTableContent = function(count) { |
||||
it('should have table content', function() { |
||||
for (var x = 1; x <= count; x++) { |
||||
var column0 = angular.element(element.find('.workpackages table tbody:nth-of-type(' + x + ') tr td:nth-child(1)')); |
||||
var column1 = angular.element(element.find('.workpackages table tbody:nth-of-type(' + x + ') tr td:nth-child(2)')); |
||||
var column2 = angular.element(element.find('.workpackages table tbody:nth-of-type(' + x + ') tr td:nth-child(3)')); |
||||
|
||||
expect(angular.element(column0).text()).to.include('Subject ' + x); |
||||
expect(angular.element(column1).text()).to.include('Status ' + x); |
||||
expect(angular.element(column2).text()).to.include('Assignee ' + x); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeCollapsedRelationsDirective = function() { |
||||
|
||||
shouldBehaveLikeRelationDirective(); |
||||
|
||||
it('should be initially collapsed', function() { |
||||
var content = angular.element(element.find('div.content')); |
||||
expect(content.hasClass('ng-hide')).to.eq(true); |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeExpandedRelationsDirective = function() { |
||||
|
||||
shouldBehaveLikeRelationDirective(); |
||||
|
||||
it('should be initially expanded', function() { |
||||
var content = angular.element(element.find('div.content')); |
||||
expect(content.hasClass('ng-hide')).to.eq(false); |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeSingleRelationDirective = function() { |
||||
it('should not have an elements count', function() { |
||||
var title = angular.element(element.find('h3')); |
||||
|
||||
expect(title.text()).not.to.include('('); |
||||
expect(title.text()).not.to.include(')'); |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeMultiRelationDirective = function() { |
||||
it('should have an elements count', function() { |
||||
var title = angular.element(element.find('h3')); |
||||
|
||||
expect(title.text()).to.include('(' + scope.relations.length + ')'); |
||||
}); |
||||
}; |
||||
|
||||
describe('no element markup', function() { |
||||
describe('single element behavior', function() { |
||||
beforeEach(function() { |
||||
compile(singleElementHtml); |
||||
}); |
||||
|
||||
shouldBehaveLikeSingleRelationDirective(); |
||||
|
||||
shouldBehaveLikeCollapsedRelationsDirective(); |
||||
}); |
||||
|
||||
describe('multi element behavior', function() { |
||||
beforeEach(function() { |
||||
scope.relations = []; |
||||
|
||||
compile(multiElementHtml); |
||||
}); |
||||
|
||||
shouldBehaveLikeMultiRelationDirective(); |
||||
|
||||
shouldBehaveLikeCollapsedRelationsDirective(); |
||||
}); |
||||
}); |
||||
|
||||
describe('single element markup', function() { |
||||
beforeEach(function() { |
||||
scope.relations = [workPackage1]; |
||||
|
||||
compile(singleElementHtml); |
||||
}); |
||||
|
||||
shouldBehaveLikeRelationDirective(); |
||||
|
||||
shouldBehaveLikeSingleRelationDirective(); |
||||
|
||||
shouldBehaveLikeExpandedRelationsDirective(); |
||||
|
||||
shouldBehaveLikeHasTableHeader(); |
||||
|
||||
shouldBehaveLikeHasTableContent(1); |
||||
}); |
||||
|
||||
describe('multi element markup', function() { |
||||
beforeEach(function() { |
||||
scope.relations = [workPackage1, workPackage2]; |
||||
|
||||
compile(multiElementHtml); |
||||
}); |
||||
|
||||
shouldBehaveLikeRelationDirective(); |
||||
|
||||
shouldBehaveLikeMultiRelationDirective(); |
||||
|
||||
shouldBehaveLikeExpandedRelationsDirective(); |
||||
|
||||
shouldBehaveLikeHasTableHeader(); |
||||
|
||||
shouldBehaveLikeHasTableContent(2); |
||||
}); |
||||
}); |
@ -0,0 +1,90 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2014 the OpenProject Foundation (OPF)
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License version 3.
|
||||
//
|
||||
// OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows:
|
||||
// Copyright (C) 2006-2013 Jean-Philippe Lang
|
||||
// Copyright (C) 2010-2013 the ChiliProject Team
|
||||
//
|
||||
// This program is free software; you can redistribute it and/or
|
||||
// modify it under the terms of the GNU General Public License
|
||||
// as published by the Free Software Foundation; either version 2
|
||||
// of the License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program; if not, write to the Free Software
|
||||
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
//
|
||||
// See doc/COPYRIGHT.rdoc for more details.
|
||||
//++
|
||||
|
||||
describe('attachmentFileSize Directive', function() { |
||||
var compile, element, rootScope, scope; |
||||
|
||||
beforeEach(angular.mock.module('openproject.workPackages.directives')); |
||||
beforeEach(module('templates')); |
||||
|
||||
beforeEach(inject(function($rootScope, $compile) { |
||||
var html; |
||||
html = '<td attachment-file-size attachment="attachment"></td>'; |
||||
|
||||
element = angular.element(html); |
||||
rootScope = $rootScope; |
||||
scope = $rootScope.$new(); |
||||
|
||||
compile = function() { |
||||
$compile(element)(scope); |
||||
scope.$digest(); |
||||
}; |
||||
})); |
||||
|
||||
describe('element', function() { |
||||
describe('with file size present on attachment', function(){ |
||||
beforeEach(function() { |
||||
scope.attachment = { |
||||
props: { |
||||
id: 1, |
||||
fileSize: '12340' |
||||
} |
||||
}; |
||||
|
||||
compile(); |
||||
}); |
||||
|
||||
it('should render element', function() { |
||||
expect(element.prop('tagName')).to.equal('TD'); |
||||
}); |
||||
|
||||
it('should render file size in kB', function() { |
||||
var el = element.find('span'); |
||||
expect(el.text()).to.equal('(12.34kB)'); |
||||
}); |
||||
}); |
||||
|
||||
describe('with missing file size', function(){ |
||||
beforeEach(function() { |
||||
scope.attachment = { |
||||
props: { |
||||
id: 1 |
||||
} |
||||
}; |
||||
|
||||
compile(); |
||||
}); |
||||
|
||||
it('should render 0kB', function() { |
||||
var el = element.find('span'); |
||||
expect(el.text()).to.equal('(0kB)'); |
||||
}); |
||||
}); |
||||
|
||||
}); |
||||
}); |
@ -0,0 +1,53 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'reform' |
||||
require 'reform/form/coercion' |
||||
|
||||
module API |
||||
module V3 |
||||
module WorkPackages |
||||
class RelationModel < Reform::Form |
||||
include Coercion |
||||
|
||||
# NOTE: to avoid a naming collision with DelayedJob, we define an |
||||
# explicit method here rather than relying on the #property macro. |
||||
# |
||||
# @see Relation#delay |
||||
def delay |
||||
model.delay |
||||
end |
||||
|
||||
def delay=(value) |
||||
model.delay = value |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,79 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
require 'roar/decorator' |
||||
require 'roar/representer/json/hal' |
||||
|
||||
module API |
||||
module V3 |
||||
module WorkPackages |
||||
class RelationRepresenter < Roar::Decorator |
||||
include Roar::Representer::JSON::HAL |
||||
include Roar::Representer::Feature::Hypermedia |
||||
include OpenProject::StaticRouting::UrlHelpers |
||||
|
||||
self.as_strategy = API::Utilities::CamelCasingStrategy.new |
||||
|
||||
def initialize(model, options = {}, *expand) |
||||
@current_user = options[:current_user] |
||||
@work_package = options[:work_package] |
||||
@expand = expand |
||||
|
||||
super(model) |
||||
end |
||||
|
||||
property :_type, exec_context: :decorator |
||||
|
||||
link :self do |
||||
{ href: "#{root_url}api/v3/relationships/#{represented.model.id}" } |
||||
end |
||||
|
||||
link :relatedFrom do |
||||
{ href: "#{root_url}api/v3/work_packages/#{represented.model.from_id}" } |
||||
end |
||||
|
||||
link :relatedTo do |
||||
{ href: "#{root_url}api/v3/work_packages/#{represented.model.to_id}" } |
||||
end |
||||
|
||||
property :delay, getter: -> (*) { model.delay }, render_nil: true, if: -> (*) { model.relation_type == 'precedes' } |
||||
|
||||
def _type |
||||
"Relation::#{relation_type}" |
||||
end |
||||
|
||||
private |
||||
|
||||
def relation_type |
||||
represented.model.relation_type_for(@work_package).camelize |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,19 @@ |
||||
Description: |
||||
Creates a new plugin with the given name in the specified directory |
||||
|
||||
Usage: |
||||
rails generate open_project:plugin NAME DIRECTORY |
||||
|
||||
Example: |
||||
rails generate open_project:plugin stuff ~/ |
||||
|
||||
This will create: |
||||
~/openproject-stuff |
||||
~/openproject-stuff/CHANGELOG.md |
||||
~/openproject-stuff/openproject-stuff.gemspec |
||||
~/openproject-stuff/lib |
||||
~/openproject-stuff/lib/openproject-stuff.rb |
||||
~/openproject-stuff/lib/open_project |
||||
~/openproject-stuff/lib/open_project/stuff.rb |
||||
~/openproject-stuff/lib/open_project/stuff |
||||
~/openproject-stuff/lib/open_project/stuff/engine.rb |
@ -0,0 +1,72 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
class OpenProject::PluginGenerator < Rails::Generators::Base |
||||
source_root File.expand_path('../templates', __FILE__) |
||||
|
||||
argument :plugin_name, :type => :string, :default => "openproject-new-plugin" |
||||
argument :root_folder, :type => :string, :default => "vendor/gems" |
||||
|
||||
# every public method is run when the generator is invoked |
||||
def generate_plugin |
||||
plugin_dir |
||||
lib_dir |
||||
end |
||||
|
||||
def full_name |
||||
@full_name ||= begin |
||||
"openproject-#{plugin_name}" |
||||
end |
||||
end |
||||
|
||||
private |
||||
def raise_on_params |
||||
puts plugin_name |
||||
puts root_folder |
||||
end |
||||
|
||||
def plugin_path |
||||
"#{root_folder}/openproject-#{plugin_name}" |
||||
end |
||||
|
||||
def plugin_dir |
||||
@plugin_dir ||= begin |
||||
directory('', plugin_path, :recursive => false) |
||||
end |
||||
end |
||||
|
||||
def lib_path |
||||
"#{plugin_path}/lib" |
||||
end |
||||
|
||||
def lib_dir |
||||
@lib_dir ||= begin |
||||
directory('lib', lib_path) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,19 @@ |
||||
# encoding: UTF-8 |
||||
$:.push File.expand_path("../lib", __FILE__) |
||||
|
||||
require 'open_project/<%= plugin_name %>/version' |
||||
# Describe your gem and declare its dependencies: |
||||
Gem::Specification.new do |s| |
||||
s.name = "<%= full_name %>" |
||||
s.version = OpenProject::<%= plugin_name.camelcase %>::VERSION |
||||
s.authors = "Finn GmbH" |
||||
s.email = "info@finn.de" |
||||
s.homepage = "https://www.openproject.org/projects/<%= plugin_name.gsub('_','-') %>" # TODO check this URL |
||||
s.summary = 'OpenProject <%= plugin_name.gsub('_', ' ').titleize %>' |
||||
s.description = FIXME |
||||
s.license = FIXME # e.g. "MIT" or "GPLv3" |
||||
|
||||
s.files = Dir["{app,config,db,lib}/**/*"] + %w(CHANGELOG.md README.md) |
||||
|
||||
s.add_dependency "rails", "~> 3.2.14" |
||||
end |
@ -0,0 +1,19 @@ |
||||
<!---- copyright |
||||
OpenProject Plugins Plugin |
||||
|
||||
Copyright (C) 2013 - 2014 the OpenProject Foundation (OPF) |
||||
|
||||
This program is free software; you can redistribute it and/or |
||||
modify it under the terms of the GNU General Public License version 3. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program; if not, write to the Free Software |
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
|
||||
See doc/COPYRIGHT.md for more details. |
||||
|
||||
++--> |
||||
|
||||
# Changelog |
||||
|
||||
* `#<ticket number>` Create plugin |
@ -0,0 +1,7 @@ |
||||
# OpenProject <%= plugin_name.gsub('_',' ').titlecase %> Plugin |
||||
|
||||
FIXME Add description and check issue tracker link below |
||||
|
||||
## Issue Tracker |
||||
|
||||
https://www.openproject.org/projects/<%= plugin_name.gsub('_','-') %>/work_packages |
@ -0,0 +1 @@ |
||||
require 'open_project/<%= plugin_name %>' |
@ -0,0 +1,5 @@ |
||||
module OpenProject |
||||
module <%= plugin_name.camelcase %> |
||||
require "open_project/<%= plugin_name %>/engine" |
||||
end |
||||
end |
@ -0,0 +1,5 @@ |
||||
module OpenProject |
||||
module <%= plugin_name.camelcase %> |
||||
VERSION = "0.0.1" |
||||
end |
||||
end |
@ -0,0 +1,45 @@ |
||||
#-- encoding: UTF-8 |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject |
||||
module Configuration |
||||
## |
||||
# To be included into OpenProject::Configuration in order to provide |
||||
# helper methods for easier access to certain configuration options. |
||||
module Helpers |
||||
## |
||||
# Activating this leaves omniauth as the only way to authenticate. |
||||
def disable_password_login? |
||||
value = self['disable_password_login'] |
||||
|
||||
['true', true].include? value # former to accommodate ENV |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,267 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject |
||||
module OmniAuth |
||||
## |
||||
# Provides authorization mechanisms for OmniAuth-based authentication. |
||||
module Authorization |
||||
## |
||||
# Checks whether the given user is authorized to login by calling |
||||
# all registered callbacks. If all callbacks approve the user is authorized and may log in. |
||||
def self.authorized?(auth_hash) |
||||
rejection = callbacks.find_map do |callback| |
||||
d = callback.authorize auth_hash |
||||
|
||||
if d.is_a? Decision |
||||
d if d.reject? |
||||
else |
||||
fail ArgumentError, 'Expecting Callback#authorize to return a Decision.' |
||||
end |
||||
end |
||||
|
||||
rejection || Approval.new |
||||
end |
||||
|
||||
## |
||||
# Signals that the given user has been logged in. |
||||
# |
||||
# Note: Only call if you know what you are doing. |
||||
def self.after_login!(user, auth_hash) |
||||
after_login_callbacks.each do |callback| |
||||
callback.after_login user, auth_hash |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Adds a callback to be executed before a user is logged in. |
||||
# The given callback may reject the user to prevent authorization by |
||||
# calling dec#reject(error) or approve by calling dec#approve. |
||||
# |
||||
# If not approved a user is implicitly rejected. |
||||
# |
||||
# @param opts [Hash] options for the callback registration |
||||
# @option opts [Symbol] :provider Only call for given provider |
||||
# |
||||
# @yield [decision, user, auth_hash] Callback to be executed before the user is logged in. |
||||
# @yieldparam [DecisionStore] dec object providing #approve and #reject |
||||
# @yieldparam [User] user The OpenProject user to be logged in. |
||||
# @yieldparam [AuthHash] OmniAuth authentication information including user info |
||||
# and credentials. |
||||
# @yieldreturn [Decision] A Decision indicating whether or not to authorize the user. |
||||
def self.authorize_user(opts = {}, &block) |
||||
if opts[:provider] |
||||
authorize_user_for_provider opts[:provider], &block |
||||
else |
||||
add_authorize_user_callback AuthorizationBlockCallback.new(&block) |
||||
end |
||||
end |
||||
|
||||
def self.authorize_user_for_provider(provider, &block) |
||||
callback = AuthorizationBlockCallback.new do |dec, auth_hash| |
||||
if auth_hash.provider.to_sym == provider.to_sym |
||||
block.call dec, auth_hash |
||||
else |
||||
dec.approve |
||||
end |
||||
end |
||||
|
||||
add_authorize_user_callback callback |
||||
end |
||||
|
||||
## |
||||
# Registers a callback on the event of a successful login. |
||||
# |
||||
# Called directly after logging in. |
||||
# This usually happens when the user logged in normally or was logged in |
||||
# automatically after on-the-fly registration via automated account activation. |
||||
# |
||||
# @yield [user] Callback called with the successfully logged in user. |
||||
# @yieldparam user [User] User who has been logged in. |
||||
def self.after_login(&block) |
||||
add_after_login_callback AfterLoginBlockCallback.new(&block) |
||||
end |
||||
|
||||
## |
||||
# Registers a new callback to decide whether or not a user is to be authorized. |
||||
# |
||||
# @param [AuthorizationCallback] Callback to be called upon user authorization. |
||||
def self.add_authorize_user_callback(callback) |
||||
callbacks << callback |
||||
end |
||||
|
||||
def self.callbacks |
||||
@callbacks ||= [] |
||||
end |
||||
|
||||
## |
||||
# Registers a new callback to successful user login. |
||||
# |
||||
# @param [AfterLoginCallback] Callback to be called upon successful authorization. |
||||
def self.add_after_login_callback(callback) |
||||
after_login_callbacks << callback |
||||
end |
||||
|
||||
def self.after_login_callbacks |
||||
@after_login_callbacks ||= [] |
||||
end |
||||
|
||||
## |
||||
# Performs user authorization. |
||||
class AuthorizationCallback |
||||
## |
||||
# Given an OmniAuth auth hash this decides if a user is authorized or not. |
||||
# |
||||
# @param [AuthHash] auth_hash OmniAuth authentication information including user info |
||||
# and credentials. |
||||
# |
||||
# @return [Decision] A decision indicating whether the user is authorized or not. |
||||
def authorize(auth_hash) |
||||
fail "subclass responsibility: authorize(#{auth_hash})" |
||||
end |
||||
end |
||||
|
||||
## |
||||
# A callback triggering a given block. |
||||
class AuthorizationBlockCallback < AuthorizationCallback |
||||
attr_reader :block |
||||
|
||||
def initialize(&block) |
||||
@block = block |
||||
end |
||||
|
||||
def authorize(auth_hash) |
||||
store = DecisionStore.new |
||||
block.call store, auth_hash |
||||
# failure to make a decision results in a rejection |
||||
store.decision || Rejection.new(I18n.t('user.authorization_rejected')) |
||||
end |
||||
end |
||||
|
||||
## |
||||
# A callback for reacting to a user being logged in. |
||||
class AfterLoginCallback |
||||
## |
||||
# Is called after a user has been logged in successfully. |
||||
# |
||||
# @param [User] User who has been logged in. |
||||
# @param [Omniauth::AuthHash] Omniauth authentication info including credentials. |
||||
def after_login(user, auth_hash) |
||||
fail "subclass responsibility: after_login(#{user}, #{auth_hash})" |
||||
end |
||||
end |
||||
|
||||
## |
||||
# A after_login callback triggering a given block. |
||||
class AfterLoginBlockCallback < AfterLoginCallback |
||||
attr_reader :block |
||||
|
||||
def initialize(&block) |
||||
@block = block |
||||
end |
||||
|
||||
def after_login(user, auth_hash) |
||||
block.call user, auth_hash |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Abstract base class for an authorization decision. |
||||
# Any subclass must either override #approve? or #reject? |
||||
# the both of which are defined in terms of each other. |
||||
class Decision |
||||
def approve? |
||||
!reject? |
||||
end |
||||
|
||||
def reject? |
||||
!approve? |
||||
end |
||||
|
||||
def self.approve |
||||
Approval.new |
||||
end |
||||
|
||||
def self.reject(error_message) |
||||
Rejection.new error_message |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Indicates a rejected authorization attempt. |
||||
class Rejection < Decision |
||||
attr_reader :message |
||||
|
||||
def initialize(message) |
||||
@message = message |
||||
end |
||||
|
||||
def reject? |
||||
true |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Indicates an approved authorization. |
||||
class Approval < Decision |
||||
def approve? |
||||
true |
||||
end |
||||
end |
||||
|
||||
## |
||||
# Stores a decision. |
||||
class DecisionStore |
||||
attr_accessor :decision |
||||
|
||||
def approve |
||||
self.decision = Approval.new |
||||
end |
||||
|
||||
def reject(error_message) |
||||
self.decision = Rejection.new error_message |
||||
end |
||||
end |
||||
|
||||
Enumerable.class_eval do |
||||
## |
||||
# Passes each element to the given block and returns the |
||||
# result of the block as soon as it's truthy. |
||||
def find_map(&block) |
||||
each do |e| |
||||
result = block.call e |
||||
|
||||
return result if result |
||||
end |
||||
|
||||
nil |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,35 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject |
||||
module Plugins |
||||
require 'open_project/plugins/patch_registry' |
||||
require 'open_project/plugins/load_dependency' |
||||
require 'open_project/plugins/acts_as_op_engine' |
||||
end |
||||
end |
@ -0,0 +1,172 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject::Plugins |
||||
module ActsAsOpEngine |
||||
|
||||
def self.included(base) |
||||
base.send(:define_method, :name) do |
||||
ActiveSupport::Inflector.demodulize(base).downcase |
||||
end |
||||
|
||||
# Don't use the PatchRegistry for now, as the core classes doesn't notify of class loading |
||||
# Use the old config.to_prepare method, but we can hopefully someday switch to on-demand |
||||
# patching once the PatchRegistry works. |
||||
|
||||
# base.send(:define_method, :patch) do |target, patch| |
||||
# OpenProject::Plugins::PatchRegistry.register(target, patch) |
||||
# end |
||||
|
||||
# Disable LoadDependency for the same reason |
||||
# base.send(:define_method, :load_dependent) do |target, *dependencies| |
||||
# OpenProject::Plugins::LoadDependency.register(target, *dependencies) |
||||
# end |
||||
|
||||
# Patch classes |
||||
# |
||||
# Looks for patches via autoloading in |
||||
# <plugin root>/lib/openproject/<plugin name>/patches/<patched_class>_patch.rb |
||||
# Make sure the patch module has the name the Rails autoloading expects. |
||||
# |
||||
# Example: |
||||
# patches [:IssuesController] |
||||
# This looks for OpenProject::XlsExport::Patches::IssuesControllerPatch |
||||
# in openproject/xls_export/patches/issues_controller_patch.rb |
||||
base.send(:define_method, :patches) do |patched_classes| |
||||
plugin_name = engine_name |
||||
base.config.to_prepare do |
||||
patched_classes.each do |klass_name| |
||||
plugin_module = plugin_name.sub(/^openproject_/, '').camelcase |
||||
patch = "OpenProject::#{plugin_module}::Patches::#{klass_name.to_s}Patch".constantize |
||||
klass = klass_name.to_s.constantize |
||||
klass.send(:include, patch) unless klass.included_modules.include?(patch) |
||||
end |
||||
end |
||||
end |
||||
|
||||
# Define assets provided by the plugin |
||||
base.send(:define_method, :assets) do |assets| |
||||
base.initializer "#{engine_name}.precompile_assets" do |app| |
||||
app.config.assets.precompile += assets.to_a |
||||
end |
||||
end |
||||
|
||||
# Add permitted attributes (strong_parameters) |
||||
# |
||||
# Useful when adding a field to an OpenProject core model. We discourage adding |
||||
# a field to a core model, but at the moment there's no API to do this in a better way |
||||
# and a lot of existing plugins already do it. |
||||
# |
||||
# See PermittedParams in OpenProject for available models |
||||
# |
||||
# Example: |
||||
# additional_permitted_attributes :user => [:registration_reason] |
||||
base.send(:define_method, :additional_permitted_attributes) do |attributes| |
||||
config.to_prepare do |
||||
::PermittedParams.send(:add_permitted_attributes, attributes) |
||||
end |
||||
end |
||||
|
||||
# Register a plugin with OpenProject |
||||
# |
||||
# Uses Gem specification for plugin name, author etc. |
||||
# |
||||
# gem_name: The gem name, used for querying the gem for metadata like author |
||||
# options: An options Hash, at least :requires_openproject is recommended to |
||||
# define the minimal version of OpenProject the plugin is compatible with |
||||
# Another common option is :author_url. |
||||
# block: Pass a block to the plugin (for defining permissions, menu items and the like) |
||||
base.send(:define_method, :register) do |gem_name, options, &block| |
||||
base.initializer "#{engine_name}.register_plugin" do |
||||
spec = Bundler.environment.specs[gem_name][0] |
||||
|
||||
p = Redmine::Plugin.register engine_name.to_sym do |
||||
name spec.summary |
||||
author spec.authors.kind_of?(Array) ? spec.authors[0] : spec.authors |
||||
description spec.description |
||||
version spec.version |
||||
url spec.homepage |
||||
|
||||
options.each do |name, value| |
||||
send(name, value) |
||||
end |
||||
end |
||||
p.instance_eval(&block) if (p && block) |
||||
end |
||||
|
||||
# Workaround to ensure settings are available after unloading in development mode |
||||
plugin_name = engine_name |
||||
if options.include? :settings |
||||
base.class_eval do |
||||
config.to_prepare do |
||||
Setting.create_setting("plugin_#{plugin_name}", |
||||
{'default' => options[:settings][:default], 'serialized' => true}) |
||||
Setting.create_setting_accessors("plugin_#{plugin_name}") |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
base.class_eval do |
||||
config.autoload_paths += Dir["#{config.root}/lib/"] |
||||
|
||||
config.before_configuration do |app| |
||||
# This is required for the routes to be loaded first |
||||
# as the routes should be prepended so they take precedence over the core. |
||||
app.config.paths['config/routes'].unshift File.join(config.root, "config", "routes.rb") |
||||
end |
||||
|
||||
initializer "#{engine_name}.remove_duplicate_routes", :after => "add_routing_paths" do |app| |
||||
# removes duplicate entry from app.routes_reloader |
||||
# As we prepend the plugin's routes to the load_path up front and rails |
||||
# adds all engines' config/routes.rb later, we have double loaded the routes |
||||
# This is not harmful as such but leads to duplicate routes which decreases performance |
||||
app.routes_reloader.paths.uniq! |
||||
end |
||||
|
||||
initializer "#{engine_name}.register_test_paths" do |app| |
||||
app.config.plugins_to_test_paths << self.root |
||||
end |
||||
|
||||
# adds our factories to factory girl's load path |
||||
initializer "#{engine_name}.register_factories", :after => "factory_girl.set_factory_paths" do |app| |
||||
FactoryGirl.definition_file_paths << File.expand_path(self.root.to_s + '/spec/factories') if defined?(FactoryGirl) |
||||
end |
||||
|
||||
initializer "#{engine_name}.append_migrations" do |app| |
||||
unless app.root.to_s.match root.to_s |
||||
config.paths["db/migrate"].expanded.each do |expanded_path| |
||||
app.config.paths["db/migrate"] << expanded_path |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
end |
||||
end |
@ -0,0 +1,41 @@ |
||||
#-- copyright |
||||
# OpenProject is a project management system. |
||||
# Copyright (C) 2012-2014 the OpenProject Foundation (OPF) |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License version 3. |
||||
# |
||||
# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: |
||||
# Copyright (C) 2006-2013 Jean-Philippe Lang |
||||
# Copyright (C) 2010-2013 the ChiliProject Team |
||||
# |
||||
# This program is free software; you can redistribute it and/or |
||||
# modify it under the terms of the GNU General Public License |
||||
# as published by the Free Software Foundation; either version 2 |
||||
# of the License, or (at your option) any later version. |
||||
# |
||||
# This program is distributed in the hope that it will be useful, |
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
# GNU General Public License for more details. |
||||
# |
||||
# You should have received a copy of the GNU General Public License |
||||
# along with this program; if not, write to the Free Software |
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
||||
# |
||||
# See doc/COPYRIGHT.rdoc for more details. |
||||
#++ |
||||
|
||||
module OpenProject::Plugins |
||||
module LoadDependency |
||||
def self.register(target, *dependencies) |
||||
|
||||
ActiveSupport.on_load(target) do |
||||
dependencies.each do |dependency| |
||||
require_dependency dependency |
||||
end |
||||
end |
||||
|
||||
end |
||||
end |
||||
end |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue