parent
57b04a59fc
commit
7ecad10c9b
@ -1,10 +1,6 @@ |
||||
<div class="detail-panel-description" ng-if="$ctrl.workPackage"> |
||||
<div class="detail-panel-description-content"> |
||||
<wp-relations |
||||
ng-repeat="relationGroup in $ctrl.relationGroups" |
||||
work-package="$ctrl.workPackage" |
||||
relation-group="relationGroup" |
||||
button-title="$ctrl.relationTitles[relationGroup.name]" |
||||
<wp-relations work-package="$ctrl.workPackage" |
||||
></wp-relations> |
||||
</div> |
||||
</div> |
||||
|
@ -1,30 +0,0 @@ |
||||
<form name="add_relation_form" class="form"> |
||||
<div class="choice"> |
||||
<div class="choice--select"> |
||||
<ui-select |
||||
id="relation_to_id-{{ $ctrl.relationGroup.id }}" |
||||
name="relation[to_id][{{ $ctrl.relationGroup.id }}]" |
||||
ng-model="$ctrl.wpToAddId" |
||||
title="{{ text.uiSelectTitle }}" |
||||
required |
||||
theme="select2" |
||||
tabindex="0"> |
||||
<ui-select-match tabindex="-1">{{$select.selected.to_s}}</ui-select-match> |
||||
<ui-select-choices |
||||
refresh-delay="250" |
||||
refresh="autocompleteWorkPackages($select.search)" |
||||
repeat="item.id as item in options"> |
||||
<div ng-bind="item.to_s"></div> |
||||
</ui-select-choices> |
||||
</ui-select> |
||||
</div> |
||||
<div class="choice--button"> |
||||
<button class="button" |
||||
title="{{ $ctrl.btnTitle }}" |
||||
ng-bind-html="$ctrl.btnIcon + ' ' + $ctrl.btnTitle" |
||||
ng-click="$ctrl.addRelation()" |
||||
ng-disabled="add_relation_form.$invalid"> |
||||
</button> |
||||
</div> |
||||
</div> |
||||
</form> |
@ -1,102 +0,0 @@ |
||||
// -- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
// ++
|
||||
|
||||
import {WorkPackageRelationGroup} from './wp-relation-group.service'; |
||||
import {wpTabsModule} from '../../../angular-modules'; |
||||
|
||||
var $state:ng.ui.IStateService; |
||||
var $q:ng.IQService; |
||||
|
||||
export class WorkPackageChildRelationsGroup extends WorkPackageRelationGroup { |
||||
public get canAddRelation() { |
||||
return !!this.workPackage.addChild; |
||||
} |
||||
|
||||
public canRemoveRelation() { |
||||
return !!this.workPackage.update; |
||||
} |
||||
|
||||
public getRelatedWorkPackage(relation) { |
||||
return relation.$load(); |
||||
} |
||||
|
||||
public addWpRelation():ng.IPromise<any> { |
||||
return this.workPackage.project.$load() |
||||
.then(() => { |
||||
const args = [ |
||||
'work-packages.list.new', |
||||
{ |
||||
parent_id: this.workPackage.id, |
||||
projectPath: this.workPackage.project.identifier |
||||
} |
||||
]; |
||||
|
||||
if ($state.includes('work-packages.show')) { |
||||
args[0] = 'work-packages.new'; |
||||
} |
||||
|
||||
(<any>$state).go(...args); |
||||
}); |
||||
} |
||||
|
||||
public removeWpRelation(relation) { |
||||
const deferred = $q.defer(); |
||||
const index = this.relations.indexOf(relation); |
||||
|
||||
relation.$load() |
||||
.then(workPackage => { |
||||
workPackage.parentId = null; |
||||
|
||||
workPackage.save() |
||||
.then(() => { |
||||
this.relations.splice(index, 1); |
||||
deferred.resolve(index); |
||||
}) |
||||
.catch(deferred.reject); |
||||
}) |
||||
.catch(deferred.reject); |
||||
|
||||
return deferred.promise; |
||||
} |
||||
|
||||
protected init() { |
||||
if (Array.isArray(this.workPackage.children)) { |
||||
this.relations.push(...this.workPackage.children); |
||||
} |
||||
} |
||||
} |
||||
|
||||
function wpChildRelationsGroupService(...args) { |
||||
[$state, $q] = args; |
||||
return WorkPackageChildRelationsGroup; |
||||
} |
||||
|
||||
wpChildRelationsGroupService.$inject = ['$state', '$q']; |
||||
|
||||
|
||||
wpTabsModule.factory('WorkPackageChildRelationsGroup', wpChildRelationsGroupService); |
@ -1,108 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
//++
|
||||
|
||||
import {WorkPackageRelationGroup} from './wp-relation-group.service'; |
||||
import {wpTabsModule} from '../../../angular-modules'; |
||||
import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service'; |
||||
import {WorkPackageNotificationService} from '../../wp-edit/wp-notification.service'; |
||||
import {ErrorResource} from '../../api/api-v3/hal-resources/error-resource.service'; |
||||
|
||||
var $q:ng.IQService; |
||||
var HalResource; |
||||
var PathHelper:any; |
||||
var wpCacheService:WorkPackageCacheService; |
||||
var wpNotificationsService:WorkPackageNotificationService; |
||||
|
||||
export class WorkPackageParentRelationGroup extends WorkPackageRelationGroup { |
||||
public get canAddRelation():boolean { |
||||
return !!this.workPackage.changeParent; |
||||
} |
||||
|
||||
public canRemoveRelation():boolean { |
||||
return this.canAddRelation; |
||||
} |
||||
|
||||
public getRelatedWorkPackage(relation) { |
||||
return relation.$load(); |
||||
} |
||||
|
||||
public addWpRelation(wpId:number) { |
||||
return this.changeParent(wpId).then(() => { |
||||
if (this.workPackage.parent) { |
||||
this.workPackage.parent.$load().then(parent => { |
||||
this.relations[0] = parent; |
||||
}); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
public removeWpRelation() { |
||||
return this.changeParent(null).then(() => { |
||||
this.relations.pop(); |
||||
return 0; |
||||
}); |
||||
} |
||||
|
||||
protected changeParent(parentId:number) { |
||||
var params = { |
||||
parentId: parentId, |
||||
lockVersion: this.workPackage.lockVersion |
||||
}; |
||||
|
||||
return this.workPackage.changeParent(params) |
||||
.then((wp) => { |
||||
this.workPackage = wp; |
||||
return wpCacheService.updateWorkPackage(wp); |
||||
}) |
||||
.catch(error => { |
||||
if (error instanceof ErrorResource) { |
||||
wpNotificationsService.showError(error, this.workPackage); |
||||
} |
||||
else { |
||||
wpNotificationsService.showGeneralError(); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
protected init() { |
||||
if (this.workPackage.parent) { |
||||
this.workPackage.parent.$load().then(parent => this.relations.push(parent)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
function wpParentRelationGroupService(...args) { |
||||
[$q, HalResource, PathHelper, wpCacheService, wpNotificationsService] = args; |
||||
return WorkPackageParentRelationGroup; |
||||
} |
||||
|
||||
wpParentRelationGroupService.$inject = [ |
||||
'$q', 'HalResource', 'PathHelper', 'wpCacheService', 'wpNotificationsService' |
||||
]; |
||||
|
||||
wpTabsModule.factory('WorkPackageParentRelationGroup', wpParentRelationGroupService); |
@ -1,136 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
//++
|
||||
|
||||
import {wpTabsModule} from '../../../angular-modules'; |
||||
import {WorkPackageRelationsConfigInterface} from '../wp-relations.service'; |
||||
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; |
||||
|
||||
declare var URI:any; |
||||
|
||||
var $q:ng.IQService; |
||||
var $http:ng.IHttpService; |
||||
var PathHelper:any; |
||||
|
||||
export class WorkPackageRelationGroup { |
||||
public relations = []; |
||||
|
||||
public get name():string { |
||||
return this.config.name; |
||||
} |
||||
|
||||
public get type():string { |
||||
return this.config.type; |
||||
} |
||||
|
||||
public get id():string { |
||||
return this.config.id || this.name; |
||||
} |
||||
|
||||
public get isEmpty():boolean { |
||||
return !this.relations.length; |
||||
} |
||||
|
||||
public get canAddRelation() { |
||||
return !!this.workPackage.addRelation; |
||||
} |
||||
|
||||
constructor(protected workPackage:WorkPackageResourceInterface, |
||||
protected config:WorkPackageRelationsConfigInterface) { |
||||
this.init(); |
||||
} |
||||
|
||||
public canRemoveRelation(relation):boolean { |
||||
return !!relation.remove; |
||||
} |
||||
|
||||
public getRelatedWorkPackage(relation) { |
||||
if (relation.relatedTo.href === this.workPackage.href) { |
||||
return relation.relatedFrom.$load(); |
||||
} |
||||
return relation.relatedTo.$load(); |
||||
} |
||||
|
||||
public findRelatableWorkPackages(search:string) { |
||||
const deferred = $q.defer(); |
||||
var params; |
||||
|
||||
this.workPackage.project.$load().then(() => { |
||||
params = { |
||||
q: search, |
||||
scope: 'relatable', |
||||
escape: false, |
||||
id: this.workPackage.id, |
||||
project_id: this.workPackage.project.id |
||||
}; |
||||
|
||||
$http({ |
||||
method: 'GET', |
||||
url: URI(PathHelper.workPackageJsonAutoCompletePath()).search(params).toString() |
||||
}) |
||||
.then((response:any) => deferred.resolve(response.data)) |
||||
.catch(deferred.reject); |
||||
}) |
||||
.catch(deferred.reject); |
||||
|
||||
return deferred.promise; |
||||
} |
||||
|
||||
public addWpRelation(wpId:number):ng.IPromise<any> { |
||||
return this.workPackage.addRelation({ |
||||
to_id: wpId, |
||||
relation_type: this.id |
||||
}) |
||||
.then(relation => this.relations.push(relation)); |
||||
} |
||||
|
||||
public removeWpRelation(relation) { |
||||
const index = this.relations.indexOf(relation); |
||||
|
||||
return relation.remove().then(() => { |
||||
this.relations.splice(index, 1); |
||||
return index; |
||||
}); |
||||
} |
||||
|
||||
protected init() { |
||||
const elements = this.workPackage.relations.elements; |
||||
if (Array.isArray(elements)) { |
||||
this.relations.push( |
||||
...elements.filter(relation => relation._type === this.type)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
function wpRelationGroupService(...args) { |
||||
[$q, $http, PathHelper] = args; |
||||
return WorkPackageRelationGroup; |
||||
} |
||||
|
||||
wpRelationGroupService.$inject = ['$q', '$http', 'PathHelper']; |
||||
|
||||
wpTabsModule.factory('WorkPackageRelationGroup', wpRelationGroupService); |
@ -1,61 +1,63 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
//++
|
||||
|
||||
import {wpTabsModule} from "../../../angular-modules"; |
||||
import {WorkPackageRelationsController} from "../wp-relations.directive"; |
||||
|
||||
function wpRelationRowDirective(PathHelper) { |
||||
var getFullIdentifier = (workPackage) => { |
||||
var type = ' '; |
||||
|
||||
if (workPackage.type) { |
||||
type += workPackage.type.name + ': '; |
||||
} |
||||
import {wpDirectivesModule} from '../../../angular-modules'; |
||||
import { |
||||
WorkPackageResource, |
||||
WorkPackageResourceInterface |
||||
} from '../../api/api-v3/hal-resources/work-package-resource.service'; |
||||
import {RelatedWorkPackage, RelationResource} from '../wp-relations.interfaces'; |
||||
|
||||
|
||||
class WpRelationRowDirectiveController { |
||||
public relatedWorkPackage:RelatedWorkPackage; |
||||
public relationType:string; |
||||
|
||||
return `#${workPackage.id}${type}${workPackage.subject}`; |
||||
public showRelationInfo:boolean = false; |
||||
|
||||
public userInputs = { |
||||
description: this.relatedWorkPackage.relatedBy.description, |
||||
showDescriptionEditForm: false |
||||
}; |
||||
|
||||
function wpRelationsDirectiveLink(scope) { |
||||
scope.workPackagePath = PathHelper.workPackagePath; |
||||
scope.userPath = PathHelper.userPath; |
||||
public relation:RelationResource = this.relatedWorkPackage.relatedBy; |
||||
|
||||
constructor(public I18n, |
||||
protected $scope:ng.IScope, |
||||
protected wpCacheService, |
||||
protected PathHelper, |
||||
protected wpNotificationsService, |
||||
protected WpRelationsService) { |
||||
if (this.relation) { |
||||
var relationType = this.WpRelationsService.getRelationTypeObjectByType(this.relation._type); |
||||
this.relationType = angular.isDefined(relationType) ? this.WpRelationsService.getTranslatedRelationTitle(relationType.name) : 'unknown'; |
||||
} |
||||
}; |
||||
|
||||
public toggleUserDescriptionForm() { |
||||
this.userInputs.showDescriptionEditForm = !this.userInputs.showDescriptionEditForm; |
||||
} |
||||
|
||||
scope.$ctrl.relationGroup.getRelatedWorkPackage(scope.relation) |
||||
.then(relatedWorkPackage => { |
||||
scope.relatedWorkPackage = relatedWorkPackage; |
||||
scope.fullIdentifier = getFullIdentifier(relatedWorkPackage); |
||||
scope.state = relatedWorkPackage.status.isClosed ? 'closed' : ''; |
||||
}); |
||||
public removeRelation() { |
||||
this.WpRelationsService.removeCommonRelation(this.relation) |
||||
.then(() => { |
||||
this.$scope.$emit('wp-relations.removed', this.relation); |
||||
this.wpCacheService.updateWorkPackage([this.relatedWorkPackage]); |
||||
this.wpNotificationsService.showSave(this.relatedWorkPackage); |
||||
}) |
||||
.catch(err => this.wpNotificationsService.handleErrorResponse(err, this.relatedWorkPackage)); |
||||
} |
||||
} |
||||
|
||||
function WpRelationRowDirective() { |
||||
return { |
||||
restrict: 'A', |
||||
link: wpRelationsDirectiveLink |
||||
restrict: 'E', |
||||
templateUrl: '/components/wp-relations/wp-relation-row/wp-relation-row.template.html', |
||||
replace: true, |
||||
scope: { |
||||
relatedWorkPackage: '=' |
||||
}, |
||||
controller: WpRelationRowDirectiveController, |
||||
controllerAs: '$ctrl', |
||||
bindToController: true |
||||
}; |
||||
} |
||||
|
||||
wpTabsModule.directive('wpRelationRow', wpRelationRowDirective); |
||||
wpDirectivesModule.directive('wpRelationRow', WpRelationRowDirective); |
||||
|
@ -0,0 +1,30 @@ |
||||
<div class="relation-row" ng-mouseover="$ctrl.showRelationControls = true" ng-mouseleave="$ctrl.showRelationControls = false"> |
||||
<div class="grid-block hierarchy-item"> |
||||
|
||||
<div class="grid-content medium-3 collapse"> |
||||
<span>{{$ctrl.relationType}}</span> |
||||
</div> |
||||
|
||||
<div class="grid-content medium-4 collapse" wp-single-relation ng-if="$ctrl.relatedWorkPackage"> |
||||
<a href="{{$singleRelation.workPackagePath($ctrl.relatedWorkPackage.id)}}" title="{{$singleRelation.getFullIdentifier($ctrl.relatedWorkPackage, true)}}"> |
||||
{{$singleRelation.getFullIdentifier($ctrl.relatedWorkPackage, true)}} |
||||
</a> |
||||
</div> |
||||
|
||||
<div class="grid-content medium-3 collapse wp-relations-status-field"> |
||||
<div wp-edit-form="$ctrl.relatedWorkPackage" ng-if="$ctrl.relatedWorkPackage"> |
||||
<div wp-edit-field="'status'"></div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section"> |
||||
<accessible-by-keyboard ng-show="$ctrl.showRelationControls" |
||||
ng-if="$ctrl.relation.remove" |
||||
execute="$ctrl.removeRelation($ctrl.relation)"> |
||||
<icon-wrapper icon-name="remove" |
||||
icon-title="{{::$ctrl.I18n.t('js.relation_buttons.remove')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,46 @@ |
||||
<div> |
||||
|
||||
<div class="wp-inline-create-button wp-relations-create-button -full-width" ng-if="!$relationsCreateCtrl.showRelationsCreateForm"> |
||||
<div class="grid-block"> |
||||
<div class="grid-content collapse"> |
||||
<a class="wp-inline-create--add-link relation-create" ng-click="$relationsCreateCtrl.createNewChildWorkPackage()"> |
||||
<i class="icon icon-add"></i> |
||||
<span>{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.add_new_child')}}</span> |
||||
</a> |
||||
</div> |
||||
<div class="grid-content collapse"> |
||||
<a class="wp-inline-create--add-link relation-create" ng-click="$relationsCreateCtrl.toggleRelationsCreateForm()"> |
||||
<i class="icon icon-add"></i> |
||||
<span>{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.add_existing_child')}}</span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="grid-block v-align" ng-if="$relationsCreateCtrl.showRelationsCreateForm"> |
||||
<div class="grid-content medium-10"> |
||||
<wp-relations-autocomplete |
||||
work-package="$relationsCreateCtrl.workPackage" |
||||
selected-wp-id="$relationsCreateCtrl.selectedWpId" |
||||
selected-relation-type="$relationsCreateCtrl.selectedRelationType"></wp-relations-autocomplete> |
||||
</div> |
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section relation-row"> |
||||
<accessible-by-keyboard |
||||
execute="$relationsCreateCtrl.createRelation()"> |
||||
<icon-wrapper icon-name="checkmark" |
||||
icon-title="{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.save')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
<accessible-by-keyboard |
||||
execute="$relationsCreateCtrl.toggleRelationsCreateForm()"> |
||||
<icon-wrapper icon-name="remove" |
||||
icon-title="{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.abort')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,21 @@ |
||||
<div class="grid-block v-align collapse" ng-if="$relationsCreateCtrl.showRelationsCreateForm || $relationsCreateCtrl.externalFormToggle"> |
||||
<div class="grid-content medium-10 collapse"> |
||||
<wp-relations-autocomplete |
||||
selected-wp-id="$relationsCreateCtrl.selectedWpId" |
||||
selected-relation-type="$relationsCreateCtrl.selectedRelationType"></wp-relations-autocomplete> |
||||
</div> |
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section relation-row"> |
||||
<accessible-by-keyboard |
||||
execute="$relationsCreateCtrl.createRelation()"> |
||||
<icon-wrapper icon-name="checkmark" |
||||
icon-title="{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.save')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
<accessible-by-keyboard |
||||
execute="$relationsCreateCtrl.toggleRelationsCreateForm()"> |
||||
<icon-wrapper icon-name="remove" |
||||
icon-title="{{::$relationsCreateCtrl.I18n('js.relation_buttons.abort')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
@ -0,0 +1,47 @@ |
||||
<div> |
||||
<div class="wp-inline-create-button wp-relations-create-button" |
||||
ng-if="!$relationsCreateCtrl.showRelationsCreateForm"> |
||||
<div class="grid-block"> |
||||
<div class="grid-content collapse"> |
||||
<a class="wp-inline-create--add-link relation-create" |
||||
ng-click="$relationsCreateCtrl.toggleRelationsCreateForm()"> |
||||
<i class="icon icon-add"></i> |
||||
<span>{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.add_new_relation')}}</span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="grid-block v-align" ng-if="$relationsCreateCtrl.showRelationsCreateForm"> |
||||
<div class="grid-content collapse medium-3"> |
||||
<select |
||||
class="relationTypeSelect" |
||||
ng-model="$relationsCreateCtrl.selectedRelationType" |
||||
ng-options="relationType.label for relationType in $relationsCreateCtrl.relationTypes"> |
||||
</select> |
||||
</div> |
||||
<div class="grid-content medium-7"> |
||||
<wp-relations-autocomplete |
||||
work-package="$relationsCreateCtrl.workPackage" |
||||
selected-wp-id="$relationsCreateCtrl.selectedWpId" |
||||
selected-relation-type="$relationsCreateCtrl.selectedRelationType"></wp-relations-autocomplete> |
||||
</div> |
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section relation-row"> |
||||
<accessible-by-keyboard |
||||
execute="$relationsCreateCtrl.createRelation()"> |
||||
<icon-wrapper icon-name="checkmark" |
||||
icon-title="{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.save')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
<accessible-by-keyboard |
||||
execute="$relationsCreateCtrl.toggleRelationsCreateForm()"> |
||||
<icon-wrapper icon-name="remove" |
||||
icon-title="{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.abort')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,34 @@ |
||||
<div> |
||||
<div class="wp-inline-create-button wp-relations-create-button -full-width" ng-if="!$relationsCreateCtrl.showRelationsCreateForm" style="width:100%;padding-right:25px;"> |
||||
<div class="grid-block"> |
||||
<div class="grid-content collapse"> |
||||
<a class="wp-inline-create--add-link relation-create" ng-click="$relationsCreateCtrl.toggleRelationsCreateForm()"> |
||||
<i class="icon icon-add"></i> |
||||
<span>{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.add_parent')}}</span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="grid-block v-align" ng-if="$relationsCreateCtrl.showRelationsCreateForm || $relationsCreateCtrl.externalFormToggle"> |
||||
<div class="grid-content medium-10 collapse"> |
||||
<wp-relations-autocomplete |
||||
work-package="$relationsCreateCtrl.workPackage" |
||||
selected-wp-id="$relationsCreateCtrl.selectedWpId" |
||||
selected-relation-type="$relationsCreateCtrl.selectedRelationType"></wp-relations-autocomplete> |
||||
</div> |
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section relation-row"> |
||||
<accessible-by-keyboard |
||||
execute="$relationsCreateCtrl.createRelation()"> |
||||
<icon-wrapper icon-name="checkmark" |
||||
icon-title="{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.save')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
<accessible-by-keyboard |
||||
execute="$relationsCreateCtrl.toggleRelationsCreateForm()"> |
||||
<icon-wrapper icon-name="remove" |
||||
icon-title="{{::$relationsCreateCtrl.I18n.t('js.relation_buttons.abort')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,129 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
//++
|
||||
|
||||
import {wpDirectivesModule} from '../../../../angular-modules'; |
||||
import {WorkPackageRelationsController} from "../../wp-relations.directive"; |
||||
import {WorkPackageRelationsHierarchyController} from "../../wp-relations-hierarchy/wp-relations-hierarchy.directive"; |
||||
import {WorkPackageResourceInterface} from "../../../api/api-v3/hal-resources/work-package-resource.service"; |
||||
|
||||
function wpRelationsAutocompleteDirective($q, PathHelper, $http) { |
||||
return { |
||||
restrict: 'E', |
||||
templateUrl: '/components/wp-relations/wp-relations-create/wp-relations-autocomplete/wp-relations-autocomplete.template.html', |
||||
require: ['^wpRelations', '?^wpRelationsHierarchy'], |
||||
scope: { |
||||
selectedWpId: '=', |
||||
selectedRelationType: '=', |
||||
workPackage: '=' |
||||
}, |
||||
link: function (scope, element, attrs, controllers ) { |
||||
scope.relatedWps = []; |
||||
getRelatedWorkPackages(); |
||||
|
||||
scope.onSelect = function(wpId){ |
||||
scope.selectedWpId = wpId; |
||||
}; |
||||
|
||||
scope.autocompleteWorkPackages = (term) => { |
||||
if (!term) { |
||||
return; |
||||
} |
||||
|
||||
findRelatableWorkPackages(term).then((workPackages:Array<WorkPackageResourceInterface>) => { |
||||
// reject already related work packages, self, children and parent
|
||||
// to prevent invalid relations
|
||||
scope.options = _.reject(workPackages, (wp) => { |
||||
return scope.relatedWps.indexOf(parseInt((wp.id as string))) > -1; |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
function findRelatableWorkPackages(search:string) { |
||||
const deferred = $q.defer(); |
||||
var params; |
||||
|
||||
scope.workPackage.project.$load().then(() => { |
||||
params = { |
||||
q: search, |
||||
scope: 'relatable', |
||||
escape: false, |
||||
id: scope.workPackage.id, |
||||
project_id: scope.workPackage.project.id |
||||
}; |
||||
|
||||
$http({ |
||||
method: 'GET', |
||||
url: URI(PathHelper.workPackageJsonAutoCompletePath()).search(params).toString() |
||||
}) |
||||
.then((response:any) => { |
||||
// THE JSON AUTOCOMPLETE SHOULD BE EXTENDED TO CONTAIN A REFERENCE TO THE
|
||||
// ACTUAL WP-TYPE SINCE MILESTONES MAY NOT BE A PARENT ELEMENT AND THEREFORE THEY
|
||||
// MUST BE REJECTED
|
||||
deferred.resolve(response.data); |
||||
}) |
||||
.catch(deferred.reject); |
||||
}) |
||||
.catch(deferred.reject); |
||||
|
||||
return deferred.promise; |
||||
} |
||||
|
||||
function getRelatedWorkPackages() { |
||||
/** NOTE: THIS METHOD COULD PROBABLY DONE MORE EFFICIENTLY BY THE BACKEND **/ |
||||
const wpRelationsController:WorkPackageRelationsController = controllers[0]; |
||||
const wpRelationsHierarchyController:WorkPackageRelationsHierarchyController = controllers[1]; |
||||
|
||||
let wps = [scope.workPackage.id]; |
||||
|
||||
wps = wps.concat(wpRelationsController.currentRelations.map(relation => relation.id)); |
||||
|
||||
if (scope.workPackage.parentId) { |
||||
wps.push(scope.workPackage.parentId); |
||||
} |
||||
|
||||
if (wpRelationsHierarchyController && wpRelationsHierarchyController.children) { |
||||
wps = wps.concat(wpRelationsHierarchyController.children.map(child => child.id)); |
||||
} else { |
||||
if (scope.workPackage.children) { |
||||
var childPromises = []; |
||||
if (scope.workPackage.children.length > 0) { |
||||
childPromises = childPromises.concat(scope.workPackage.children.map(child => child.$load())); |
||||
$q.all(childPromises).then(children => { |
||||
wps = wps.concat(children.map(child => child.id)); |
||||
scope.relatedWps = wps; |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
scope.relatedWps = wps; |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
|
||||
wpDirectivesModule.directive('wpRelationsAutocomplete', wpRelationsAutocompleteDirective); |
@ -0,0 +1,21 @@ |
||||
<form name="add_relation_form" class="form"> |
||||
<div class="dropdown-wrapper"> |
||||
<ui-select |
||||
class="inplace-edit--select -full-width" |
||||
ng-model="selectedWpId" |
||||
on-select="onSelect($model)" |
||||
append-to-body="true" |
||||
required |
||||
theme="select2" |
||||
tabindex="0"> |
||||
<ui-select-match tabindex="-1">{{$select.selected.to_s}}</ui-select-match> |
||||
<ui-select-choices |
||||
refresh-delay="250" |
||||
refresh="autocompleteWorkPackages($select.search)" |
||||
repeat="item.id as item in options | filter: item.config.canAdd"> |
||||
<div ng-bind="item.to_s"></div> |
||||
</ui-select-choices> |
||||
</ui-select> |
||||
</div> |
||||
</form> |
||||
|
@ -0,0 +1,117 @@ |
||||
import {wpDirectivesModule} from '../../../angular-modules'; |
||||
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; |
||||
import {RelationType} from "../wp-relations.interfaces"; |
||||
|
||||
export class WpRelationsCreateController { |
||||
|
||||
public showRelationsCreateForm: boolean = false; |
||||
public workPackage:WorkPackageResourceInterface; |
||||
public selectedRelationType:RelationType; |
||||
public selectedWpId:string; |
||||
public externalFormToggle: boolean; |
||||
public fixedRelationType:string; |
||||
public relationTypes = this.WpRelationsService.getRelationTypes(true); |
||||
public translatedRelationTitle = this.WpRelationsService.getTranslatedRelationTitle; |
||||
|
||||
protected relationTitles = this.WpRelationsService.configuration.relationTitles; |
||||
|
||||
constructor(public I18n, |
||||
protected $scope, |
||||
protected $rootScope, |
||||
protected $state, |
||||
protected WpRelationsService, |
||||
protected WpRelationsHierarchyService, |
||||
protected wpNotificationsService, |
||||
protected wpCacheService) { |
||||
|
||||
var defaultRelationType = angular.isDefined(this.fixedRelationType) ? this.fixedRelationType : 'relatedTo'; |
||||
this.selectedRelationType = this.WpRelationsService.getRelationTypeObjectByName(defaultRelationType); |
||||
|
||||
if (angular.isDefined(this.externalFormToggle)) { |
||||
this.showRelationsCreateForm = this.externalFormToggle; |
||||
} |
||||
} |
||||
|
||||
public createRelation() { |
||||
|
||||
if (!this.selectedRelationType || ! this.selectedWpId) { |
||||
return; |
||||
} |
||||
|
||||
switch (this.selectedRelationType.name) { |
||||
case 'parent': |
||||
this.changeParent(); |
||||
break; |
||||
case 'children': |
||||
this.addExistingChildRelation(); |
||||
break; |
||||
default: |
||||
this.createCommonRelation(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
protected addExistingChildRelation() { |
||||
this.WpRelationsHierarchyService.addExistingChildWp(this.workPackage, this.selectedWpId) |
||||
.then(newChildWp => this.$scope.$emit('wp-relations.addedChild', newChildWp)) |
||||
.catch(err => this.wpNotificationsService.handleErrorResponse(err, this.workPackage)) |
||||
.finally(this.toggleRelationsCreateForm()); |
||||
} |
||||
|
||||
protected createNewChildWorkPackage() { |
||||
this.WpRelationsHierarchyService.addNewChildWp(this.workPackage); |
||||
} |
||||
|
||||
protected changeParent() { |
||||
this.WpRelationsHierarchyService.changeParent(this.workPackage, this.selectedWpId) |
||||
.then(updatedWp => { |
||||
this.$rootScope.$broadcast('wp-relations.changedParent', { |
||||
updatedWp: updatedWp, |
||||
parentId: this.selectedWpId |
||||
}); |
||||
this.wpNotificationsService.showSave(this.workPackage); |
||||
}) |
||||
.catch(err => this.wpNotificationsService.handleErrorResponse(err, this.workPackage)) |
||||
.finally(this.toggleRelationsCreateForm()); |
||||
} |
||||
|
||||
protected createCommonRelation() { |
||||
let relationType = this.selectedRelationType.name === 'relatedTo' ? this.selectedRelationType.id : this.selectedRelationType.name; |
||||
|
||||
this.WpRelationsService.addCommonRelation(this.workPackage, relationType, this.selectedWpId) |
||||
.then(relation => { |
||||
this.$scope.$emit('wp-relations.added', relation); |
||||
this.wpNotificationsService.showSave(this.workPackage); |
||||
}) |
||||
.catch(err => this.wpNotificationsService.handleErrorResponse(err, this.workPackage)) |
||||
.finally(() => this.toggleRelationsCreateForm()); |
||||
} |
||||
|
||||
public toggleRelationsCreateForm() { |
||||
this.showRelationsCreateForm = !this.showRelationsCreateForm; |
||||
this.externalFormToggle = !this.externalFormToggle; |
||||
} |
||||
} |
||||
|
||||
function wpRelationsCreate() { |
||||
return { |
||||
restrict: 'E', |
||||
replace: true, |
||||
|
||||
templateUrl: (el, attrs) => { |
||||
return '/components/wp-relations/wp-relations-create/' + attrs.template + '.template.html'; |
||||
}, |
||||
|
||||
scope: { |
||||
workPackage: '=?', |
||||
fixedRelationType: '@?', |
||||
externalFormToggle: '=?' |
||||
}, |
||||
|
||||
controller: WpRelationsCreateController, |
||||
bindToController: true, |
||||
controllerAs: '$relationsCreateCtrl', |
||||
}; |
||||
} |
||||
|
||||
wpDirectivesModule.directive('wpRelationsCreate', wpRelationsCreate); |
@ -0,0 +1,16 @@ |
||||
<div class="attributes-group"> |
||||
<div class="attributes-group--header"> |
||||
<div class="attributes-group--header-container"> |
||||
<h3 class="attributes-group--header-text"> |
||||
{{$ctrl.wpType}} |
||||
</h3> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="content" ng-if="$ctrl.relatedWorkPackages"> |
||||
<wp-relation-row |
||||
related-work-package="relatedWorkPackage" |
||||
ng-repeat="relatedWorkPackage in $ctrl.relatedWorkPackages"></wp-relation-row> |
||||
</div> |
||||
</div> |
||||
|
@ -0,0 +1,75 @@ |
||||
import {wpDirectivesModule} from '../../../angular-modules'; |
||||
import { |
||||
WorkPackageResource, |
||||
WorkPackageResourceInterface |
||||
} from "../../api/api-v3/hal-resources/work-package-resource.service"; |
||||
|
||||
|
||||
class WpRelationsHierarchyRowDirectiveController { |
||||
public workPackage; |
||||
public relatedWorkPackage; |
||||
public relationType; |
||||
public showEditForm: boolean = false; |
||||
public workPackagePath = this.PathHelper.workPackagePath; |
||||
|
||||
constructor(public I18n, |
||||
protected $scope, |
||||
protected WpRelationsHierarchyService, |
||||
protected wpNotificationsService, |
||||
protected wpCacheService, |
||||
protected PathHelper, |
||||
protected wpNotificationsService) { |
||||
|
||||
if (!this.relatedWorkPackage && this.relationType !== 'parent') { |
||||
this.relatedWorkPackage = angular.copy(this.workPackage); |
||||
} |
||||
}; |
||||
|
||||
public removeRelation() { |
||||
if (this.relationType === 'child') { |
||||
this.removeChild(); |
||||
|
||||
}else if (this.relationType === 'parent') { |
||||
this.removeParent(); |
||||
} |
||||
} |
||||
|
||||
protected removeChild() { |
||||
this.WpRelationsHierarchyService.removeChild(this.relatedWorkPackage).then(exChildWp => { |
||||
this.$scope.$emit('wp-relations.removedChild', exChildWp); |
||||
this.wpNotificationsService.showSave(this.workPackage); |
||||
}) |
||||
.catch(err => this.wpNotificationsService.handleErrorResponse(err, this.relatedWorkPackage));; |
||||
} |
||||
|
||||
protected removeParent() { |
||||
this.WpRelationsHierarchyService.removeParent(this.workPackage) |
||||
.then((updatedWp) => { |
||||
this.$scope.$emit('wp-relations.changedParent', { |
||||
updatedWp: this.workPackage, |
||||
parentId: null |
||||
}); |
||||
this.wpNotificationsService.showSave(this.workPackage); |
||||
}) |
||||
.catch(err => this.wpNotificationsService.handleErrorResponse(err, this.relatedWorkPackage));; |
||||
} |
||||
} |
||||
|
||||
function WpRelationsHierarchyRowDirective() { |
||||
return { |
||||
restrict: 'E', |
||||
templateUrl: '/components/wp-relations/wp-relations-hierarchy-row/wp-relations-hierarchy-row.template.html', |
||||
replace: true, |
||||
scope: { |
||||
indentBy: '@?', |
||||
workPackage: '=', |
||||
relatedWorkPackage: '=?', |
||||
relationType: '@' |
||||
}, |
||||
controller: WpRelationsHierarchyRowDirectiveController, |
||||
controllerAs: '$ctrl', |
||||
bindToController: true |
||||
}; |
||||
} |
||||
|
||||
wpDirectivesModule.directive('wpRelationsHierarchyRow', WpRelationsHierarchyRowDirective); |
@ -0,0 +1,41 @@ |
||||
<div class="relation-row" ng-mouseover="$ctrl.showRelationControls = true" ng-mouseleave="$ctrl.showRelationControls = false"> |
||||
<div class="grid-block v-align hierarchy-item" ng-if="!$ctrl.showEditForm && $ctrl.relatedType !== 'parent' && $ctrl.relatedWorkPackage"> |
||||
<div class="grid-content medium-6 collapse" wp-single-relation> |
||||
<span ng-style="{'padding-left': $ctrl.indentBy + 'px'}"> |
||||
<a href="{{$singleRelation.workPackagePath($ctrl.relatedWorkPackage.id)}}"> |
||||
{{$singleRelation.getFullIdentifier($ctrl.relatedWorkPackage)}} |
||||
</a> |
||||
</span> |
||||
</div> |
||||
<div class="grid-content medium-4 collapse wp-relations-status-field"> |
||||
<div wp-edit-form="$ctrl.relatedWorkPackage" ng-if="$ctrl.relatedWorkPackage"> |
||||
<div wp-edit-field="'status'"> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="grid-content medium-2 collapse wp-relations-controls-section"> |
||||
<div ng-hide="!$ctrl.showRelationControls"> |
||||
<accessible-by-keyboard ng-hide="$ctrl.relationType !== 'parent'" |
||||
execute="$ctrl.showEditForm = true"> |
||||
<icon-wrapper icon-name="edit" |
||||
icon-title="{{::$ctrl.I18n.t('js.relation_buttons.change_parent')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
|
||||
<accessible-by-keyboard ng-hide="!$ctrl.relationType" |
||||
execute="$ctrl.removeRelation()"> |
||||
<icon-wrapper icon-name="remove" |
||||
icon-title="{{::$ctrl.I18n.t('js.relation_buttons.remove')}}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div ng-if="($ctrl.relationType === 'parent' && !$ctrl.relatedWorkPackage) || $ctrl.showEditForm"> |
||||
<wp-relations-create template="empty-parents" |
||||
fixed-relation-type="parent" |
||||
external-form-toggle="$ctrl.showEditForm" |
||||
work-package="$ctrl.workPackage"></wp-relations-create> |
||||
</div> |
||||
</div> |
@ -0,0 +1,130 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
//++
|
||||
|
||||
import {wpDirectivesModule} from '../../../angular-modules'; |
||||
import {WorkPackageResourceInterface} from '../../api/api-v3/hal-resources/work-package-resource.service'; |
||||
import {WorkPackageCacheService} from '../../work-packages/work-package-cache.service'; |
||||
|
||||
export class WorkPackageRelationsHierarchyController { |
||||
public workPackage:WorkPackageResourceInterface; |
||||
public parent:WorkPackageResourceInterface; |
||||
public children:WorkPackageResourceInterface[] = []; |
||||
public showEditForm:boolean = false; |
||||
public workPackagePath = this.PathHelper.workPackagePath; |
||||
public canHaveChildren = !this.workPackage.isMilestone; |
||||
|
||||
constructor(public I18n, |
||||
protected $scope:ng.IScope, |
||||
protected $rootScope:ng.IRootScopeService, |
||||
protected $q:ng.IQService, |
||||
protected PathHelper, |
||||
protected wpCacheService:WorkPackageCacheService) { |
||||
|
||||
this.registerEventListeners(); |
||||
|
||||
if (angular.isNumber(this.workPackage.parentId)) { |
||||
this.loadParents(); |
||||
} |
||||
|
||||
if (this.workPackage.children) { |
||||
this.loadChildren(); |
||||
} |
||||
} |
||||
|
||||
protected loadParents() { |
||||
this.wpCacheService.loadWorkPackage(this.workPackage.parentId) |
||||
.take(1) |
||||
.subscribe((parent:WorkPackageResourceInterface) => { |
||||
this.parent = parent; |
||||
}); |
||||
} |
||||
|
||||
protected loadChildren() { |
||||
let relatedChildrenPromises = this.workPackage.children.map(child => child.$load()); |
||||
|
||||
this.$q.all(relatedChildrenPromises).then((children:Array<WorkPackageResourceInterface>) => { |
||||
this.children = children; |
||||
}); |
||||
} |
||||
|
||||
protected removedChild(evt, removedChild) { |
||||
_.remove(this.children, {'id' : removedChild.id}); |
||||
this.wpCacheService.updateWorkPackageList([this.workPackage, removedChild]); |
||||
this.$rootScope.$emit('workPackagesRefreshInBackground'); |
||||
} |
||||
|
||||
protected addedChild(evt, addedChildWorkPackage) { |
||||
this.children.push(addedChildWorkPackage); |
||||
this.wpCacheService.updateWorkPackageList([this.workPackage, addedChildWorkPackage]); |
||||
this.$rootScope.$emit('workPackagesRefreshInBackground'); |
||||
} |
||||
|
||||
private registerEventListeners() { |
||||
this.$scope.$on('wp-relations.changedParent', this.updatedParent.bind(this)); |
||||
this.$scope.$on('wp-relations.removedChild', this.removedChild.bind(this)); |
||||
this.$scope.$on('wp-relations.addedChild', this.addedChild.bind(this)); |
||||
} |
||||
|
||||
private updatedParent(evt, changedData) { |
||||
if (changedData.parentId !== null) { |
||||
// parent changed
|
||||
this.wpCacheService.loadWorkPackage(changedData.parentId, true) |
||||
.take(1) |
||||
.subscribe((parent:WorkPackageResourceInterface) => { |
||||
this.parent = parent; |
||||
|
||||
this.wpCacheService.updateWorkPackageList([this.workPackage, parent]); |
||||
this.$rootScope.$emit('workPackagesRefreshInBackground'); |
||||
}); |
||||
} else { |
||||
// parent deleted
|
||||
this.$rootScope.$emit('workPackagesRefreshInBackground'); |
||||
this.parent = null; |
||||
} |
||||
this.workPackage = changedData.updatedWp; |
||||
} |
||||
} |
||||
|
||||
function wpRelationsDirective() { |
||||
return { |
||||
restrict: 'E', |
||||
replace: true, |
||||
templateUrl: '/components/wp-relations/wp-relations-hierarchy/wp-relations-hierarchy.template.html', |
||||
|
||||
scope: { |
||||
workPackage: '=', |
||||
relationType: '@' |
||||
}, |
||||
|
||||
controller: WorkPackageRelationsHierarchyController, |
||||
controllerAs: '$ctrl', |
||||
bindToController: true, |
||||
}; |
||||
} |
||||
|
||||
wpDirectivesModule.directive('wpRelationsHierarchy', wpRelationsDirective); |
@ -0,0 +1,89 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
//++
|
||||
|
||||
import {wpDirectivesModule} from '../../../angular-modules'; |
||||
|
||||
|
||||
|
||||
export class WorkPackageRelationsHierarchyService { |
||||
constructor(protected $state, |
||||
protected $q, |
||||
protected wpCacheService) { |
||||
|
||||
} |
||||
|
||||
public changeParent(workPackage, parentId) { |
||||
workPackage.parentId = (parentId as number); |
||||
return workPackage.save(); |
||||
} |
||||
|
||||
public removeParent(workPackage) { |
||||
return this.changeParent(workPackage, null); |
||||
} |
||||
|
||||
public addExistingChildWp(workPackage, childWpId) { |
||||
let deferred = this.$q.defer(); |
||||
this.wpCacheService.loadWorkPackage([childWpId]) |
||||
.take(1) |
||||
.subscribe(wpToBecomeChild => { |
||||
wpToBecomeChild.parentId = workPackage.id; |
||||
deferred.resolve(wpToBecomeChild.save()); |
||||
}); |
||||
return deferred.promise; |
||||
} |
||||
|
||||
public addNewChildWp(workPackage) { |
||||
workPackage.project.$load() |
||||
.then(() => { |
||||
const args = [ |
||||
'work-packages.list.new', |
||||
{ |
||||
parent_id: workPackage.id, |
||||
projectPath: workPackage.project.identifier |
||||
} |
||||
]; |
||||
|
||||
if (this.$state.includes('work-packages.show')) { |
||||
args[0] = 'work-packages.new'; |
||||
} |
||||
|
||||
(<any>this.$state).go(...args); |
||||
}); |
||||
} |
||||
|
||||
public removeChild(childWorkPackage) { |
||||
childWorkPackage.parentId = null; |
||||
return childWorkPackage.save(); |
||||
} |
||||
|
||||
|
||||
} |
||||
|
||||
wpDirectivesModule.service('WpRelationsHierarchyService', WorkPackageRelationsHierarchyService); |
||||
|
||||
|
@ -0,0 +1,19 @@ |
||||
<div class="wp-relations-hierarchy-section"> |
||||
<div class="attributes-group--header"> |
||||
<div class="attributes-group--header-container"> |
||||
<h3 class="attributes-group--header-text"> |
||||
{{::this.$ctrl.I18n.t('js.relations_hierarchy.hierarchy_headline')}} |
||||
</h3> |
||||
</div> |
||||
</div> |
||||
|
||||
<wp-relations-hierarchy-row work-package="$ctrl.workPackage" related-work-package="$ctrl.parent" relation-type="parent"></wp-relations-hierarchy-row> |
||||
<wp-relations-hierarchy-row work-package="$ctrl.workPackage" indent-by="20"></wp-relations-hierarchy-row> |
||||
<wp-relations-hierarchy-row work-package="$ctrl.workPackage" related-work-package="relatedWorkPackage" indent-by="40" relation-type="child" ng-repeat="relatedWorkPackage in $ctrl.children"></wp-relations-hierarchy-row> |
||||
|
||||
<wp-relations-create |
||||
fixed-relation-type="children" |
||||
work-package="$ctrl.workPackage" |
||||
template="add-child" |
||||
ng-if="$ctrl.workPackage.addRelation && $ctrl.canHaveChildren"></wp-relations-create> |
||||
</div> |
@ -1,93 +0,0 @@ |
||||
<div ng-class="['relation', $ctrl.relationGroup.id]"> |
||||
<h3> |
||||
<accessible-by-keyboard execute="$ctrl.toggleExpand()" |
||||
link-class="{{ $ctrl.relationGroup.id }}-toggle-link"> |
||||
<i class="icon-pull-content" ng-class="$ctrl.stateClass"></i> {{ $ctrl.text.title }} |
||||
<span ng-if="$ctrl.relationGroup.id !== 'parent'"> |
||||
({{ $ctrl.relationGroup.relations.length }}) |
||||
</span> |
||||
</accessible-by-keyboard> |
||||
</h3> |
||||
<div class="content" ng-if="$ctrl.groupExpanded"> |
||||
<div class="workpackages"> |
||||
<div ng-if="!$ctrl.relationGroup.isEmpty"> |
||||
<table class="attributes-table"> |
||||
<colgroup> |
||||
<col style="width: 50%"/> |
||||
<col style="width: 15%"/> |
||||
<col/> |
||||
<col style="width: 1rem"/> |
||||
</colgroup> |
||||
<thead> |
||||
<tr> |
||||
<th title="{{ $ctrl.text.table.subject }}">{{ $ctrl.text.table.subject }}</th> |
||||
<th title="{{ $ctrl.text.table.status }}">{{ $ctrl.text.table.status }}</th> |
||||
<th title="{{ $ctrl.text.table.assignee }}">{{ $ctrl.text.table.assignee }}</th> |
||||
<th></th> |
||||
</tr> |
||||
</thead> |
||||
<tbody> |
||||
<tr wp-relation-row |
||||
ng-repeat="relation in $ctrl.relationGroup.relations"> |
||||
<td focus="$ctrl.isFocused($index)"> |
||||
<a title="{{ fullIdentifier }}" class="work_package" ng-class="state" |
||||
href="{{ workPackagePath(relatedWorkPackage.id) }}"> |
||||
{{ fullIdentifier }} |
||||
</a> |
||||
</td> |
||||
<td title="{{ relatedWorkPackage.status.name }}"> |
||||
{{ relatedWorkPackage.status.name }} |
||||
</td> |
||||
<td> |
||||
<a |
||||
ng-if="relatedWorkPackage.assignee |
||||
&& relatedWorkPackage.assignee.subtype != 'Group'" |
||||
title="{{ relatedWorkPackage.assignee.name }}" |
||||
href="{{ userPath(relatedWorkPackage.assignee.id) }}"> |
||||
{{ relatedWorkPackage.assignee.name }} |
||||
</a> |
||||
<span ng-if="relatedWorkPackage.assignee |
||||
&& relatedWorkPackage.assignee.subtype == 'Group'"> |
||||
{{ relatedWorkPackage.assignee.name }} |
||||
</span> |
||||
<empty-element ng-if="!relatedWorkPackage.assignee"></empty-element> |
||||
</td> |
||||
<td class="icon"> |
||||
<accessible-by-keyboard ng-if="$ctrl.canRemoveRelation(relation)" |
||||
execute="$ctrl.removeRelation(relation)"> |
||||
<icon-wrapper icon-name="remove" |
||||
icon-title="{{ $ctrl.text.relations.remove }}"> |
||||
</icon-wrapper> |
||||
</accessible-by-keyboard> |
||||
</td> |
||||
</tr> |
||||
|
||||
</tbody> |
||||
</table> |
||||
</div> |
||||
<div ng-if="$ctrl.relationGroup.isEmpty"> |
||||
{{ $ctrl.text.relations.empty }} |
||||
</div> |
||||
</div> |
||||
<div |
||||
class="add-relation" |
||||
ng-if="$ctrl.relationGroup.canAddRelation" |
||||
ng-switch="$ctrl.relationGroup.type" |
||||
focus="$ctrl.isFocused(-1)"> |
||||
|
||||
<!-- Add WP child --> |
||||
<button |
||||
ng-switch-when="children" |
||||
class="button add-work-package-child-button" |
||||
title="{{ $ctrl.btnTitle }}" |
||||
ng-bind-html="$ctrl.btnIcon + ' ' + $ctrl.btnTitle" |
||||
ng-click="$ctrl.addRelation()" |
||||
focus="$ctrl.isFocused(-1)" |
||||
></button> |
||||
|
||||
|
||||
<!-- Add WP relation --> |
||||
<add-wp-relation ng-switch-default></add-wp-relation> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -1,344 +0,0 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
//++
|
||||
|
||||
|
||||
import {wpTabsModule, opApiModule} from '../../angular-modules'; |
||||
const expect = chai.expect; |
||||
|
||||
describe('Work Package Relations Directive', () => { |
||||
var $q; |
||||
var I18n; |
||||
var compile; |
||||
var element; |
||||
var scope; |
||||
var stateParams = {}; |
||||
var WorkPackageChildRelationsGroup; |
||||
|
||||
var workPackage; |
||||
var relation; |
||||
var relationGroupMock; |
||||
var canAdd; |
||||
var canRemove; |
||||
|
||||
beforeEach(angular.mock.module( |
||||
wpTabsModule.name, |
||||
opApiModule.name, |
||||
'openproject.helpers', |
||||
'openproject.models', |
||||
'openproject.layout', |
||||
'openproject.services', |
||||
'openproject.viewModels', |
||||
'ngSanitize')); |
||||
|
||||
beforeEach(angular.mock.module('openproject.templates', function ($provide) { |
||||
var configurationService = { |
||||
isTimezoneSet: sinon.stub().returns(false), |
||||
accessibilityModeEnabled: sinon.stub().returns(false) |
||||
}; |
||||
|
||||
$provide.constant('$stateParams', stateParams); |
||||
$provide.constant('ConfigurationService', configurationService); |
||||
})); |
||||
|
||||
beforeEach(angular.mock.inject(($rootScope, |
||||
$compile, |
||||
_$q_, |
||||
_I18n_, |
||||
_WorkPackageChildRelationsGroup_) => { |
||||
$q = _$q_; |
||||
I18n = _I18n_; |
||||
WorkPackageChildRelationsGroup = _WorkPackageChildRelationsGroup_; |
||||
|
||||
scope = $rootScope.$new(); |
||||
|
||||
compile = html => { |
||||
element = $compile(html)(scope); |
||||
scope.$digest(); |
||||
}; |
||||
|
||||
relation = { |
||||
subject: 'Subject 1', |
||||
assignee: { |
||||
name: 'Assignee 1' |
||||
}, |
||||
status: { |
||||
name: 'Status 1', |
||||
isClosed: false |
||||
}, |
||||
$load: () => $q.when(relation) |
||||
}; |
||||
workPackage = {}; |
||||
canAdd = true; |
||||
canRemove = true; |
||||
relationGroupMock = { |
||||
id: 'parent', |
||||
type: 'parent', |
||||
name: 'parent', |
||||
relations: [relation], |
||||
getRelatedWorkPackage: () => relation.$load(), |
||||
canAddRelation: () => canAdd, |
||||
canRemoveRelation: () => canRemove, |
||||
isEmpty: false, |
||||
}; |
||||
|
||||
var stub = sinon.stub(I18n, 't'); |
||||
stub.withArgs('js.work_packages.properties.subject').returns('Subject'); |
||||
stub.withArgs('js.work_packages.properties.status').returns('Status'); |
||||
stub.withArgs('js.work_packages.properties.assignee').returns('Assignee'); |
||||
stub.withArgs('js.relations.remove').returns('Remove relation'); |
||||
stub.withArgs('js.relation_labels.parent').returns('Parent'); |
||||
stub.withArgs('js.relation_labels.something').returns('Something'); |
||||
})); |
||||
|
||||
afterEach(() => { |
||||
I18n.t.restore(); |
||||
}); |
||||
|
||||
var html = `<wp-relations relation-group="relationGroup"
|
||||
button-title="'Add Relation'"></wp-relations>`; |
||||
|
||||
var shouldBehaveLikeRelationsDirective = () => { |
||||
it('should have a title', () => { |
||||
const title = angular.element(element.find('h3')); |
||||
const text = relationGroupMock.id === 'something' ? 'Something' : 'Parent'; |
||||
expect(title.text()).to.include(text); |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeHasTableHeader = () => { |
||||
it('should have a table head', () => { |
||||
var column0 = angular.element(element.find('.workpackages table thead th:nth-child(1)')); |
||||
var column1 = angular.element(element.find('.workpackages table thead th:nth-child(2)')); |
||||
var column2 = angular.element(element.find('.workpackages table thead th: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 = (removable) => { |
||||
it('should have table content', () => { |
||||
let x = 1; |
||||
var column0 = element.find('.workpackages tr:nth-of-type(' + x + ') td:nth-child(1)'); |
||||
var column1 = element.find('.workpackages tr:nth-of-type(' + x + ') td:nth-child(2)'); |
||||
var column2 = element.find('.workpackages tr:nth-of-type(' + x + ') td:nth-child(3)'); |
||||
|
||||
expect(column0.text()).to.include('Subject ' + x); |
||||
expect(column1.text()).to.include('Status ' + x); |
||||
expect(column2.text()).to.include('Assignee ' + x); |
||||
|
||||
expect(column0.find('a').hasClass('work_package')).to.be.true; |
||||
expect(column0.find('a').hasClass('closed')).to.be.false; |
||||
|
||||
if (removable) { |
||||
const column4 = element.find('.workpackages table tbody tr:nth-of-type(' + x + ') td:nth-child(4)'); |
||||
const removeIcon = column4.find('span.icon-remove'); |
||||
expect(removeIcon.length).not.to.eq(0); |
||||
expect(removeIcon.attr('title')).to.include('Remove relation'); |
||||
} |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeCollapsedRelationsDirective = () => { |
||||
|
||||
shouldBehaveLikeRelationsDirective(); |
||||
|
||||
it('should be initially collapsed', () => { |
||||
var content = angular.element(element.find('div.content')); |
||||
expect(content.hasClass('ng-if')).to.eq(false); |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeExpandedRelationsDirective = () => { |
||||
|
||||
shouldBehaveLikeRelationsDirective(); |
||||
|
||||
it('should be initially expanded', () => { |
||||
var content = angular.element(element.find('div.content')); |
||||
expect(content.hasClass('ng-hide')).to.eq(false); |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeSingleRelationDirective = () => { |
||||
it('should NOT have an elements count', () => { |
||||
let len = scope.relationGroup.length; |
||||
expect(element.find('h3').text()).to.not.include('(' + len + ')'); |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeMultiRelationDirective = () => { |
||||
it('should have an elements count', () => { |
||||
let len = scope.relationGroup.relations.length; |
||||
expect(element.find('h3').text()).to.include('(' + len + ')'); |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeHasAddRelationDialog = () => { |
||||
it('should have an add relation button and id input', () => { |
||||
const addRelationDiv = element.find('.content .add-relation'); |
||||
const button = addRelationDiv.find('button'); |
||||
|
||||
expect(addRelationDiv).not.to.eq(0); |
||||
expect(button.attr('title')).to.include('Add Relation'); |
||||
expect(button.text()).to.include('Add Relation'); |
||||
}); |
||||
}; |
||||
|
||||
var shouldBehaveLikeReadOnlyRelationDialog = () => { |
||||
it('should have no add relation button and id input', () => { |
||||
var addRelationDiv = element.find('.workpackages .add-relation'); |
||||
expect(addRelationDiv.length).to.eq(0); |
||||
}); |
||||
}; |
||||
|
||||
describe('when having child relations', () => { |
||||
var childGroupConfig; |
||||
|
||||
beforeEach(() => { |
||||
childGroupConfig = { |
||||
name: 'children', |
||||
type: 'children' |
||||
}; |
||||
}); |
||||
|
||||
context('when it is possible to add a child relation', () => { |
||||
beforeEach(() => { |
||||
workPackage = { |
||||
addChild: true, |
||||
children: [relation], |
||||
}; |
||||
scope.relationGroup = |
||||
new WorkPackageChildRelationsGroup(workPackage, childGroupConfig); |
||||
|
||||
compile(html); |
||||
}); |
||||
|
||||
it('should have an "add child" button', () => { |
||||
expect(element.find('.add-work-package-child-button').length).to.eq(1); |
||||
}); |
||||
}); |
||||
|
||||
context('when it is not possible to add a child relation', () => { |
||||
beforeEach(() => { |
||||
scope.relationGroup = |
||||
new WorkPackageChildRelationsGroup({}, childGroupConfig); |
||||
compile(html); |
||||
}); |
||||
|
||||
it('should have no add child link', () => { |
||||
expect(angular.element(element.find('.add-work-package-child-button')).length).to.eq(0); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
describe('when there is no element markup', () => { |
||||
beforeEach(() => { |
||||
canAdd = true; |
||||
relationGroupMock.id = 'something'; |
||||
scope.relationGroup = relationGroupMock; |
||||
|
||||
compile(html); |
||||
}); |
||||
|
||||
shouldBehaveLikeMultiRelationDirective(); |
||||
shouldBehaveLikeCollapsedRelationsDirective(); |
||||
shouldBehaveLikeHasAddRelationDialog(); |
||||
}); |
||||
|
||||
describe('single element markup', () => { |
||||
describe('header', () => { |
||||
beforeEach(() => { |
||||
scope.relationGroup = relationGroupMock; |
||||
compile(html); |
||||
}); |
||||
|
||||
shouldBehaveLikeSingleRelationDirective(); |
||||
}); |
||||
|
||||
describe('when it is readonly', () => { |
||||
beforeEach(() => { |
||||
canRemove = true; |
||||
scope.relationGroup = relationGroupMock; |
||||
compile(html); |
||||
}); |
||||
|
||||
shouldBehaveLikeRelationsDirective(); |
||||
shouldBehaveLikeExpandedRelationsDirective(); |
||||
shouldBehaveLikeHasTableHeader(); |
||||
shouldBehaveLikeHasTableContent(true); |
||||
shouldBehaveLikeReadOnlyRelationDialog(); |
||||
}); |
||||
|
||||
describe('when it is possible to add and remove relations', () => { |
||||
beforeEach(() => { |
||||
scope.relationGroup = relationGroupMock; |
||||
compile(html); |
||||
}); |
||||
|
||||
shouldBehaveLikeRelationsDirective(); |
||||
shouldBehaveLikeExpandedRelationsDirective(); |
||||
shouldBehaveLikeHasTableHeader(); |
||||
shouldBehaveLikeHasTableContent(false); |
||||
shouldBehaveLikeHasAddRelationDialog(); |
||||
}); |
||||
|
||||
describe('when the work package is closed', () => { |
||||
beforeEach(() => { |
||||
relation.status.isClosed = true; |
||||
scope.relationGroup = relationGroupMock; |
||||
compile(html); |
||||
}); |
||||
|
||||
it('should have set the css class of the row to closed', () => { |
||||
var closedWorkPackageRow = element.find('.workpackages tr:nth-of-type(1) td:nth-child(1) a'); |
||||
expect(closedWorkPackageRow.hasClass('closed')).to.be.true; |
||||
}); |
||||
}); |
||||
|
||||
describe('when a table row has no work package assigned', () => { |
||||
var row; |
||||
|
||||
beforeEach(() => { |
||||
relation.assignee = null; |
||||
scope.relationGroup = relationGroupMock; |
||||
|
||||
compile(html); |
||||
row = element.find('.workpackages tr:nth-of-type(1)'); |
||||
}); |
||||
|
||||
it('should NOT have link', () => { |
||||
expect(row.find('td:nth-of-type(2) a').length).to.eql(0); |
||||
}); |
||||
|
||||
it('should have empty element tag', () => { |
||||
expect(row.find('empty-element').text()).to.include('-'); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
@ -0,0 +1,28 @@ |
||||
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service'; |
||||
import {HalResource} from '../api/api-v3/hal-resources/hal-resource.service'; |
||||
|
||||
export interface RelatedWorkPackage extends WorkPackageResourceInterface { |
||||
relatedBy: RelationResource; |
||||
} |
||||
|
||||
export interface RelatedWorkPackagesGroup { |
||||
[key: string] : Array<RelatedWorkPackage>; |
||||
} |
||||
|
||||
export interface RelationResource extends HalResource { |
||||
_type: string; |
||||
description: string; |
||||
updateRelation(params:Object): ng.IPromise<any>; |
||||
remove(): ng.IPromise<any>; |
||||
} |
||||
|
||||
export interface RelationType { |
||||
name: string; |
||||
id?: string; |
||||
type: string; |
||||
} |
||||
|
||||
export interface RelationTitle { |
||||
[key: string]: string; |
||||
} |
||||
|
@ -0,0 +1,15 @@ |
||||
<div> |
||||
<div ng-repeat="(type, relatedWorkPackages) in $ctrl.relationGroups"> |
||||
<wp-relations-group wp-type="type" |
||||
related-work-packages="relatedWorkPackages" |
||||
work-package="$ctrl.workPackage"></wp-relations-group> |
||||
</div> |
||||
<wp-relations-create related-work-packages="$ctrl.currentRelations" |
||||
work-package="$ctrl.workPackage" |
||||
template="dynamic-relation-types" |
||||
ng-if="$ctrl.workPackage.addRelation"></wp-relations-create> |
||||
|
||||
<wp-relations-hierarchy work-package="$ctrl.workPackage"></wp-relations-hierarchy> |
||||
</div> |
||||
|
||||
|
@ -0,0 +1,60 @@ |
||||
//-- copyright
|
||||
// OpenProject is a project management system.
|
||||
// Copyright (C) 2012-2015 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.
|
||||
//++
|
||||
|
||||
import {wpDirectivesModule} from '../../angular-modules'; |
||||
import {WorkPackageResourceInterface} from '../api/api-v3/hal-resources/work-package-resource.service'; |
||||
import {RelationResource} from './wp-relations.interfaces'; |
||||
/** |
||||
* Contains methods and attributes shared |
||||
* between common relations and parent-child relations |
||||
*/ |
||||
export class WorkPackageSingleRelationController { |
||||
public workPackagePath = this.PathHelper.workPackagePath; |
||||
|
||||
constructor(protected PathHelper) { |
||||
} |
||||
|
||||
public getFullIdentifier(workPackage:WorkPackageResourceInterface, hideType?:boolean) { |
||||
var type = ''; |
||||
if (workPackage.type && !hideType) { |
||||
type += workPackage.type.name + ': '; |
||||
} |
||||
return `${type}${workPackage.subject}`; |
||||
} |
||||
} |
||||
|
||||
function wpSingleRelationDirective() { |
||||
return { |
||||
restrict: 'A', |
||||
controller: WorkPackageSingleRelationController, |
||||
controllerAs: '$singleRelation', |
||||
bindToController: true, |
||||
}; |
||||
} |
||||
|
||||
wpDirectivesModule.directive('wpSingleRelation', wpSingleRelationDirective); |
Loading…
Reference in new issue