Complex priorities UI in stories

This commit adds a list of worklists that the current user is
subscribed to which contain the story or any task within the story
to the story view. This is a basic way of displaying complex
priorities for the story.

This commit also removes the priority dropdown, since we don't want
to support simple/global priority in StoryBoard really.

Story: 329
Task: 2987
Change-Id: I399ac5017db9644ceddcaaba48775e6e2d9d0158
This commit is contained in:
Adam Coldrick 2016-05-04 17:57:20 +00:00
parent 64bbab44e1
commit c43d2b3d45
9 changed files with 251 additions and 32 deletions

View File

@ -30,7 +30,7 @@
<i class="fa fa-caret-right"></i>
</a>
<br ng-if="!!minimalPager" />
<span class="btn-group" dropdown>
<span ng-if="!!onPageSize" class="btn-group" dropdown>
<div>
<button class="btn btn-xs btn-default"
dropdown-toggle>

View File

@ -1,5 +1,6 @@
/*
* Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
* Copyright (c) 2016 Codethink Ltd.
*
* Licensed under the Apache License, Version 2.0 (the 'License'); you may
* not use this file except in compliance with the License. You may obtain
@ -23,7 +24,7 @@ angular.module('sb.story').controller('StoryDetailController',
Story, Project, Branch, creator, tasks, Task, DSCacheFactory,
User, $q, storyboardApiBase, SessionModalService, moment,
$document, $anchorScroll, $timeout, $location, currentUser,
enableEditableComments, Tags) {
enableEditableComments, Tags, worklists) {
'use strict';
var pageSize = Preference.get('story_detail_page_size');
@ -85,8 +86,7 @@ angular.module('sb.story').controller('StoryDetailController',
story_id: $scope.story.id,
branch_id: branch.id,
project_id: project.id,
status: 'todo',
priority: 'medium'
status: 'todo'
});
$scope.projects[project.name]
.branchNames.push(branch.name);
@ -97,6 +97,61 @@ angular.module('sb.story').controller('StoryDetailController',
angular.forEach(tasks, mapTaskToProject);
/**
* All worklists containing this story or tasks within it, with
* information about which task is relevant added.
*
* @type {[Worklist]}
*/
function setWorklists() {
function isNotArchived(card) {
return !card.archived;
}
var taskIds = $scope.tasks.map(function(task) {
return task.id;
});
for (var i = 0; i < worklists.length; i++) {
var worklist = worklists[i];
worklist.relatedItems = [];
worklist.items = worklist.items.filter(isNotArchived);
for (var j = 0; j < worklist.items.length; j++) {
var item = worklist.items[j];
if (item.item_type === 'story') {
if (item.item_id === story.id) {
worklist.relatedItems.push(item);
}
} else if (item.item_type === 'task') {
if (taskIds.indexOf(item.item_id) > -1) {
worklist.relatedItems.push(item);
}
}
}
}
$scope.worklists = worklists.map(function(list) {
if (list.relatedItems.length > 0) {
return list;
}
}).filter(function(list) { return list; });
}
setWorklists();
$scope.showWorklistsModal = function() {
var modalInstance = $modal.open({
templateUrl: 'app/stories/template/worklists.html',
controller: 'StoryWorklistsController',
resolve: {
worklists: function () {
return $scope.worklists;
}
}
});
// Return the modal's promise.
return modalInstance.result;
};
// Load the preference for each display event.
function reloadPagePreferences() {
TimelineEventTypes.forEach(function (type) {
@ -531,8 +586,7 @@ angular.module('sb.story').controller('StoryDetailController',
*/
$scope.newTask = new Task({
story_id: $scope.story.id,
status: 'todo',
priority: 'medium'
status: 'todo'
});
/**

View File

@ -0,0 +1,33 @@
/*
* Copyright (c) 2016 Codethink Ltd.
*
* Licensed under the Apache License, Version 2.0 (the 'License'); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an 'AS IS' BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Modal to display worklists related to stories
*/
angular.module('sb.story').controller('StoryWorklistsController',
function($scope, $modalInstance, worklists) {
'use strict';
/**
* Close this modal.
*/
$scope.close = function () {
$modalInstance.dismiss('cancel');
};
$scope.worklists = worklists;
}
);

View File

@ -75,6 +75,19 @@ angular.module('sb.story', ['ui.router', 'sb.services', 'sb.util',
return {};
});
return user;
},
worklists: function(Worklist, $stateParams, CurrentUser) {
var userPromise = CurrentUser.resolve();
return userPromise.then(function(user) {
return Worklist.browse({
story_id: $stateParams.storyId,
subscriber_id: user.id,
hide_lanes: ''
}).$promise;
}, function() {
return [];
});
}
}
});

View File

@ -23,6 +23,14 @@
<strong>This story is private</strong>.
Edit this story to change the privacy settings.
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<div class="hidden-xs hidden-sm col-sm-3 pull-right"
ng-show="worklists.length > 0 && !showEditForm">
<div ng-include src="'/inline/worklists.html'"></div>
</div>
<div ng-include
src="'/inline/story_detail.html'"
ng-hide="showEditForm">
@ -31,6 +39,15 @@
src="'/inline/story_detail_form.html'"
ng-show="showEditForm">
</div>
<hr class="visible-sm" />
<div class="col-xs-12 visible-sm"
ng-show="worklists.length > 0 && !showEditForm">
<div ng-include src="'/inline/worklists.html'"></div>
</div>
</div>
</div>
<div class="row">
<div class="col-xs-12">
<hr/>
<div ng-include src="'/inline/task_list.html'"></div>
<hr/>
@ -257,6 +274,58 @@
</form>
</script>
<!-- Template for the list of relevant worklists -->
<script type="text/ng-template" id="/inline/worklists.html">
<table class="table table-striped table-condensed">
<thead>
<tr>
<th>In worklists you subscribed to</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="worklist in worklists.slice(0, 5)">
<td>
<a href="/#!/worklist/{{worklist.id}}">{{worklist.title}}</a>
<span class="text-muted pull-right" ng-show="worklist.automatic">
(automatic)
</span>
<div ng-click="showItems = !showItems">
<a href>
<i class="fa"
ng-class="{'fa-caret-right': !showItems,
'fa-caret-down': showItems}"></i>
<small>
{{showItems ? 'Hide' : 'Show'}} items...
</small>
</a>
</div>
<table class="table table-striped table-condensed subtable" ng-show="showItems">
<tr ng-repeat="item in worklist.relatedItems | orderBy: 'list_position'">
<td>
{{item.item_type | capitalize}} {{item.item_id}}
<span class="pull-right">
{{item.list_position + 1}} of {{worklist.items.length}}
</span>
</td>
</tr>
</table>
</td>
</tr>
<tr ng-if="worklists.length > 5">
<td class="text-center">
<a href ng-click="showWorklistsModal()">More...</a>
</td>
</tr>
</tbody>
<tbody ng-if="worklists.length < 1">
<tr>
<td colspan="2">No worklists related to this story</td>
</tr>
</tbody>
</table>
</script>
<!-- Template for the tags list and controls -->
<script type="text/ng-template" id="/inline/tags.html">
<strong>Tags</strong>
@ -383,7 +452,7 @@
/>
</div>
</div>
<div class="col-xs-2">
<div class="col-xs-3">
<user-typeahead
ng-model="task.assignee_id"
enabled="isLoggedIn"
@ -400,13 +469,6 @@
status="{{ task.status }}"
/>
</div>
<div class="col-xs-2">
<task-priority-dropdown
editable="{{isLoggedIn}}"
on-change="updateTask(task, 'priority', priority)"
priority="{{newTask.priority}}"
/>
</div>
<!-- Review link should go here once implemented in the API -->
</div>
<div class="row button-row" ng-show="showDetail">
@ -520,7 +582,7 @@
placeholder="Enter task name"
/>
</div>
<div class="col-xs-2">
<div class="col-xs-3">
<user-typeahead
auto-focus="false"
ng-model="projects[name].branches[branchName].newTask.assignee_id"
@ -539,15 +601,8 @@
status="{{projects[name].branches[branchName].newTask.status}}"
/>
</div>
<div class="col-xs-2">
<task-priority-dropdown
editable="{{isLoggedIn}}"
on-change="updateTask(projects[name].branches[branchName].newTask, 'priority', priority)"
priority="{{projects[name].branches[branchName].newTask.priority}}"
/>
</div>
<!-- Review link should go here once implemented in the API -->
<div class="col-xs-2 text-right">
<div class="col-xs-2 text-right col-xs-offset-1">
<button ng-click="createTask(projects[name].branches[branchName].newTask,
projects[name].branches[branchName])"
class="btn btn-primary">
@ -616,7 +671,7 @@
placeholder="Enter task name"
/>
</div>
<div class="col-xs-2">
<div class="col-xs-3">
<user-typeahead
auto-focus="false"
ng-model="newTask.assignee_id"
@ -636,14 +691,7 @@
/>
</div>
<!-- Review link should go here once implemented in the API -->
<div class="col-xs-2">
<task-priority-dropdown
editable="{{isLoggedIn}}"
on-change="updateTask(newTask, 'priority', priority)"
priority="{{newTask.priority}}"
/>
</div>
<div class="col-xs-2 text-right">
<div class="col-xs-2 text-right col-xs-offset-1">
<button ng-click="createTask(newTask)"
class="btn btn-primary">
Save

View File

@ -0,0 +1,41 @@
<div class="panel panel-default">
<div class="panel-heading">
<button type="button" class="close" aria-hidden="true"
ng-click="close()">&times;</button>
<h3 class="panel-title">In worklists you're subscribed to</h3>
</div>
<div class="panel-body">
<table class="table table-striped">
<tbody>
<tr ng-repeat="worklist in worklists">
<td>
<a href="/#!/worklist/{{worklist.id}}">{{worklist.title}}</a>
<span class="text-muted pull-right" ng-show="worklist.automatic">
(automatic)
</span>
<div ng-click="showItems = !showItems">
<a href>
<i class="fa"
ng-class="{'fa-caret-right': !showItems,
'fa-caret-down': showItems}"></i>
<small>
{{showItems ? 'Hide' : 'Show'}} items...
</small>
</a>
</div>
<table class="table table-striped table-condensed subtable" ng-show="showItems">
<tr ng-repeat="item in worklist.relatedItems | orderBy: 'list_position'">
<td>
{{item.item_type | capitalize}} {{item.item_id}}
<span class="pull-right">
{{item.list_position + 1}} of {{worklist.items.length}}
</span>
</td>
</tr>
</table>
</td>
</tr>
</tbody>
</table>
</div>
</div>

View File

@ -76,3 +76,7 @@ tr.selected-row {
background-color: @brand-primary !important;
color: @navbar-default-color;
}
.subtable {
margin-bottom: 0;
}

View File

@ -0,0 +1,25 @@
/*
* Copyright (c) 2016 Codethink Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License. You may obtain
* a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Story view styles
*/
/* "Related Worklists" table style */
.position-label {
margin-left: 5px;
}

View File

@ -50,3 +50,4 @@
@import './base/edit_tasks.less';
@import './base/boards_worklists.less';
@import './base/calendar.less';
@import './base/stories.less';