Attach issues to sprints

stable
Daniel García 2018-07-27 13:06:08 +02:00 committed by Alex Hermida
parent 572ba7a8b9
commit 2d5f18c8d4
13 changed files with 169 additions and 60 deletions

View File

@ -61,7 +61,7 @@ class ConfirmService extends taiga.Service
# Render content
el.find(".title").text(title) if title
el.find(".subtitle").text(subtitle) if subtitle
el.find(".message").text(message) if message
el.find(".message").html(message) if message
# Assign event handlers
el.on "click.confirm-dialog", ".button-green", debounce 2000, (event) =>

View File

@ -840,6 +840,9 @@ $confirm, $q, attachmentsService, $template, $compile) ->
}
}
$scope.setMode = (value) ->
$scope.mode = value
$scope.$on "genericform:new", (ctx, params) ->
getSchema(params)
$scope.mode = 'new'
@ -851,9 +854,9 @@ $confirm, $q, attachmentsService, $template, $compile) ->
$scope.mode = 'add-existing'
$scope.getOrCreate = true
$scope.existingFilterText = ''
$scope.existingItems = []
$rs[schema.model].listInAllProjects({ project: $scope.project.id }, true).then (data) ->
$scope.existingItems = data
$scope.existingItems = angular.copy(data)
mount(params)
$scope.$on "genericform:edit", (ctx, params) ->
@ -885,6 +888,7 @@ $confirm, $q, attachmentsService, $template, $compile) ->
form.reset() if form
resetAttachments()
setStatus($scope.obj.status)
render()
$scope.lightboxOpen = true
lightboxService.open($el)
@ -950,6 +954,9 @@ $confirm, $q, attachmentsService, $template, $compile) ->
lightboxService.close($el)
$rootScope.$broadcast("#{$scope.objType}form:add:success", item)
$scope.isDisabledExisting = (item) ->
return item && item[$scope.relatedField] == $scope.relatedObjectId
$scope.addExisting = (selectedItem) ->
event.preventDefault()
addExisting(selectedItem)
@ -976,7 +983,7 @@ $confirm, $q, attachmentsService, $template, $compile) ->
deleteAttachments(data).then () ->
createAttachments(data).then () ->
currentLoading.finish()
close()
lightboxService.close($el)
$rs[schema.model].getByRef(data.project, data.ref, schema.params).then (obj) ->
$rootScope.$broadcast(broadcastEvent, obj)
promise.then null, (data) ->
@ -987,20 +994,14 @@ $confirm, $q, attachmentsService, $template, $compile) ->
checkClose = () ->
if !$scope.obj.isModified()
close()
lightboxService.close($el)
$scope.$apply ->
$scope.obj.revert()
else
$confirm.ask(
$translate.instant("LIGHTBOX.CREATE_EDIT.CONFIRM_CLOSE")).then (result) ->
result.finish()
close()
close = () ->
delete $scope.objType
delete $scope.mode
$scope.lightboxOpen = false
lightboxService.close($el)
lightboxService.close($el)
$el.on "submit", "form", submit
@ -1060,9 +1061,14 @@ $confirm, $q, attachmentsService, $template, $compile) ->
$scope.selectedStatus = _.find $scope.statusList, (item) -> item.id == id
$scope.obj.is_closed = $scope.selectedStatus.is_closed
render = (sprint) ->
template = $template.get("common/lightbox/lightbox-create-edit/lb-create-edit.html")
templateScope = $scope.$new()
compiledTemplate = $compile(template)(templateScope)
$el.html(compiledTemplate)
return {
link: link
templateUrl: "common/lightbox/lightbox-create-edit/lb-create-edit.html"
}
module.directive("tgLbCreateEdit", [

View File

@ -704,22 +704,47 @@ module.directive("tgPromoteIssueToUsButton", ["$rootScope", "$tgRepo", "$tgConfi
## Add Issue to Sprint button directive
#############################################################################
AssignSprintToIssueButtonDirective = ($rootScope, $rs, $repo, $loading, $translate, lightboxService) ->
AssignSprintToIssueButtonDirective = ($rootScope, $rs, $repo, $loading, $translate,
lightboxService, $confirm) ->
link = ($scope, $el, $attrs, $model) ->
avaliableMilestones = []
issue = null
$el.on "click", "a", (event) ->
$el.on "click", ".assign-issue-button.button-unset", (event) ->
event.preventDefault()
event.stopPropagation()
title = $translate.instant("ISSUES.ACTION_ASSIGN_SPRINT")
title = $translate.instant("ISSUES.ACTION_ATTACH_SPRINT")
issue = $model.$modelValue
$rs.sprints.list($scope.projectId, null).then (data) ->
$scope.milestones = data.milestones
$scope.selectedSprintId = issue.milestone
$scope.selectedSprint = issue.milestone
avaliableMilestones = angular.copy($scope.milestones)
lightboxService.open($el.find(".lightbox-assign-sprint-to-issue"))
$el.on "click", ".assign-issue-button.button-set", (event) ->
event.preventDefault()
event.stopPropagation()
issue = $model.$modelValue
$rs.sprints.list($scope.projectId, null).then (data) ->
currentSprint = _.find(data.milestones, { "id": issue.milestone })
title = $translate.instant("ISSUES.CONFIRM_DETACH_FROM_SPRINT.TITLE")
message = $translate.instant("ISSUES.CONFIRM_DETACH_FROM_SPRINT.MESSAGE")
message += " <strong>#{currentSprint.name}</strong>"
$confirm.ask(title, null, message).then (askResponse) ->
onSuccess = ->
$scope.$broadcast("assign-sprint-to-issue:success", null)
askResponse.finish()
lightboxService.close($el)
onError = ->
askResponse.finish(false)
$confirm.notify("error")
issue.setAttr('milestone', null)
$repo.save(issue, true).then(onSuccess, onError)
$scope.$on "$destroy", ->
$el.off()
@ -750,5 +775,5 @@ AssignSprintToIssueButtonDirective = ($rootScope, $rs, $repo, $loading, $transla
}
module.directive("tgAssignSprintToIssueButton", ["$rootScope", "$tgResources", "$tgRepo",
"$tgLoading", "$translate", "lightboxService",
"$tgLoading", "$translate", "lightboxService", "$tgConfirm"
AssignSprintToIssueButtonDirective] )

View File

@ -564,8 +564,8 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin, taiga
@rs.issues.getByRef(issue.getIn(['model', 'project']), issue.getIn(['model', 'ref']))
.then (removingIssue) =>
issue = issue.set('loading-delete', false)
title = @translate.instant("ISSUES.CONFIRM_REMOVE_FROM_SPRINT.TITLE")
subtitle = @translate.instant("ISSUES.CONFIRM_REMOVE_FROM_SPRINT.MESSAGE")
title = @translate.instant("ISSUES.CONFIRM_DETACH_FROM_SPRINT.TITLE")
subtitle = @translate.instant("ISSUES.CONFIRM_DETACH_FROM_SPRINT.MESSAGE")
message = removingIssue.subject
@confirm.askOnDelete(title, message, subtitle).then (askResponse) =>
removingIssue.milestone = null

View File

@ -1102,7 +1102,7 @@
"CONFIRM_CLOSE": "You have not saved changes.\nAre you sure you want to close the form?",
"EXISTING_OBJECT": "Existing {{ objName }}",
"NEW_OBJECT": "New {{ objName }}",
"CHOOSE_EXISTING": "What's the {{ objName }}?",
"CHOOSE_EXISTING": "Which {{ objName }}?",
"NO_ITEMS_FOUND": "It looks like nothing was found with your search criteria"
},
"DELETE_DUE_DATE": {
@ -1164,11 +1164,11 @@
"CREATE_RELATED_USERSTORIES": "Create a relationship with",
"NEW_USERSTORY": "New user story",
"EXISTING_USERSTORY": "Existing user story",
"CHOOSE_PROJECT_FOR_CREATION": "What's the project?",
"CHOOSE_PROJECT_FOR_CREATION": "Which project?",
"SUBJECT": "Subject",
"SUBJECT_BULK_MODE": "Subject (bulk insert)",
"CHOOSE_PROJECT_FROM": "What's the project?",
"CHOOSE_USERSTORY": "What's the user story?",
"CHOOSE_PROJECT_FROM": "Which project?",
"CHOOSE_USERSTORY": "Which user story?",
"NO_USERSTORIES": "This project has no User Stories yet. Please select another project.",
"NO_USERSTORIES_FOUND": "It looks like nothing was found with your search criteria",
"FILTER_USERSTORIES": "Filter user stories",
@ -1424,7 +1424,8 @@
"SECTION_NAME": "Issue",
"ACTION_NEW_ISSUE": "+ NEW ISSUE",
"ACTION_PROMOTE_TO_US": "Promote to User Story",
"ACTION_ASSIGN_SPRINT": "Add issue to Sprint",
"ACTION_ATTACH_SPRINT": "Attach issue to Sprint",
"ACTION_DETACH_SPRINT": "Detach issue from Sprint",
"ACTION_REMOVE_FROM_SPRINT": "Remove issue from Sprint {{ sprintName }}",
"PROMOTED": "This issue has been promoted to US:",
"EXTERNAL_REFERENCE": "This issue has been created from",
@ -1434,15 +1435,16 @@
"LINK_TASKBOARD": "Taskboard",
"TITLE_LINK_TASKBOARD": "Go to the taskboard",
"FILTER_SPRINTS": "Filter Sprints",
"CHOOSE_SPRINT": "What's the Sprint?",
"CHOOSE_SPRINT": "Which Sprint?",
"FIELDS": {
"PRIORITY": "Priority",
"SEVERITY": "Severity",
"TYPE": "Type"
},
"CONFIRM_REMOVE_FROM_SPRINT": {
"TITLE": "Remove issue from sprint",
"MESSAGE": "Are you sure you want to remove this issue from the sprint?"
"FILTER_ISSUES": "Filter Issues",
"CONFIRM_DETACH_FROM_SPRINT": {
"TITLE": "Detach issue from Sprint",
"MESSAGE": "You are about to detach the issue from the sprint"
},
"CONFIRM_PROMOTE": {
"TITLE": "Promote this issue to a new user story",

View File

@ -39,7 +39,7 @@ dueDatePopoverDirective = ($translate, datePickerConfigService) ->
return
event.preventDefault()
event.stopPropagation()
if !el.picker.getDate()
if !el.picker.getDate() && ctrl.dueDate
el.picker.setDate(moment(ctrl.dueDate).format('YYYY-MM-DD'))
el.find(".date-picker-popover").popover().open()

View File

@ -33,6 +33,13 @@ searchListDirective = ($translate) ->
if scope.itemType == 'issue'
scope.milestonesById = groupBy(scope.project.milestones, (e) -> e.id)
if scope.filterClosed
scope.showClosed = false
if scope.itemType == 'sprint'
scope.textShowClosed = $translate.instant("BACKLOG.SPRINTS.ACTION_SHOW_CLOSED_SPRINTS")
scope.textHideClosed = $translate.instant("BACKLOG.SPRINTS.ACTION_HIDE_CLOSED_SPRINTS")
el.on "click", ".choice", (event) ->
choiceId = parseInt($(event.currentTarget).data("choice-id"))
value = if attrs.ngModel?.id != choiceId then itemsById[choiceId] else null
@ -48,30 +55,45 @@ searchListDirective = ($translate) ->
value = value.toString()
return normalizeString(value.toUpperCase())
el.on "blur", "#items-filter", (event) ->
filtering = false
resetSelected = () ->
scope.currentSelected = null
model.$setViewValue(null)
el.on "focus", "#items-filter", (event) ->
filtering = true
resetAll = () ->
resetSelected()
scope.searchText = ''
avaliableItems = angular.copy(scope.items)
itemsById = groupBy(avaliableItems, (x) -> x.id)
scope.isVisible = (item) ->
if !scope.filterClosed || scope.showClosed
return true
if (scope.itemType == 'sprint' && (item.closed || item.is_closed))
if (scope.currentSelected?.id == item.id)
resetSelected()
return false
return true
scope.toggleShowClosed = (item) ->
scope.showClosed = !scope.showClosed
scope.filterItems = (searchText) ->
scope.items = avaliableItems.filter((item) ->
scope.itemDisabled(null)
scope.filtering = true
scope.items = _.filter(avaliableItems, (item) ->
itemAttrs = item.getAttrs()
if Array.isArray(scope.filterBy)
_.some(scope.filterBy, (attr) -> isContainedIn(searchText, itemAttrs[attr]))
else
isContainedIn(searchText, itemAttrs[scope.filterBy])
)
if scope.value
scope.value = _.find(scope.items, scope.currentSelected)
if !_.find(scope.items, scope.currentSelected)
resetSelected()
scope.$watch 'items', (items) ->
if !filtering && items?.length
if scope.resetOnChange
scope.currentSelected = null
model.$setViewValue(null)
avaliableItems = angular.copy(items)
itemsById = groupBy(avaliableItems, (x) -> x.id)
if !scope.filtering && items
resetAll()
return {
link: link,
@ -84,7 +106,8 @@ searchListDirective = ($translate) ->
filterBy: '=',
items: '=',
itemType: '@',
resetOnChange: "="
filterClosed: '=',
itemDisabled: '='
}
}

View File

@ -2,6 +2,18 @@ fieldset.search-list
label(
for="items-filter"
) {{ label }}
a.show-closed(
href=""
ng-if="showClosedVisible"
ng-click="toggleShowClosed()"
)
span(ng-if="!showClosed")
tg-svg(svg-icon="icon-archive")
span {{ textShowClosed }}
span(ng-if="showClosed")
tg-svg(svg-icon="icon-archive")
span {{ textHideClosed }}
input.items-filter(
id="items-filter"
type="text"
@ -9,12 +21,13 @@ fieldset.search-list
ng-model="searchText"
ng-change="filterItems(searchText)"
)
ul
li.choice(
ng-repeat="item in items track by item.id"
ng-class="{ 'selected': item.id == currentSelected.id }"
data-choice-id="{{ item.id }}"
ng-if="isVisible(item)"
ng-disabled="itemDisabled(item)"
)
ng-include(src="templateUrl")

View File

@ -1,4 +1,16 @@
.search-list {
.show-closed {
align-content: center;
align-items: center;
display: flex;
float: right;
font-size: .9em;
svg {
height: 1em;
margin-right: .25em;
width: 1em;
}
}
ul {
background: $mass-white;
border: 1px solid $gray-light;
@ -7,7 +19,7 @@
max-height: 200px;
overflow-y: auto;
}
li {
.choice {
cursor: pointer;
padding: .25em .5em;
&.selected {
@ -17,6 +29,13 @@
color: $white;
}
}
&[disabled] {
color: $gray-lighter;
cursor: not-allowed;
.info {
color: $gray-lighter;
}
}
ng-include {
display: flex;
width: 100%;
@ -25,7 +44,7 @@
text-align: left;
}
.info {
color: $gray-lighter;
color: $gray-light;
text-align: right;
}
}

View File

@ -4,10 +4,10 @@ form(ng-if="lightboxOpen")
h2.title(ng-switch="mode")
span(ng-switch-when="new") {{ 'LIGHTBOX.CREATE_EDIT.NEW' | translate: { objName: objName } }}
span(ng-switch-when="edit") {{ 'LIGHTBOX.CREATE_EDIT.EDIT' | translate: { objName: objName } }}
span(ng-switch-when="add-existing") {{ 'LIGHTBOX.CREATE_EDIT.ADD_EXISTING' | translate: { objName: objName, targetName: existingOptions.title } }}
span(ng-switch-when="add-existing") {{ 'LIGHTBOX.CREATE_EDIT.ADD_EXISTING' | translate: { objName: objName, targetName: title } }}
.existing-or-new-selector(ng-show="getOrCreate == true")
.existing-or-new-selector-single
.existing-or-new-selector-single(ng-click="setMode('add-existing')")
input(
type="radio"
name="related-with-selector"
@ -15,10 +15,10 @@ form(ng-if="lightboxOpen")
value="add-existing"
ng-model="mode"
)
label.e2e-existing-user-story-label(for="add-existing")
label.e2e-existing-item-label(for="add-existing")
span.name {{ 'LIGHTBOX.CREATE_EDIT.EXISTING_OBJECT' | translate: { objName: objName } }}
.existing-or-new-selector-single
.existing-or-new-selector-single(ng-click="setMode('new')")
input(
type="radio"
name="related-with-selector"
@ -26,20 +26,20 @@ form(ng-if="lightboxOpen")
value="new"
ng-model="mode"
)
label.e2e-new-userstory-label(for="new")
label.e2e-new-item-label(for="new")
span.name {{ 'LIGHTBOX.CREATE_EDIT.NEW_OBJECT' | translate: { objName: objName } }}
div(ng-if="mode == 'add-existing'")
.existing-item-wrapper
tg-search-list(
label="{{ 'LIGHTBOX.CREATE_EDIT.CHOOSE_EXISTING' | translate: { objName: objName } }}"
placeholder="{{ 'ISSUES.FILTER_SPRINTS' | translate }}"
placeholder="{{ 'ISSUES.FILTER_ISSUES' | translate }}"
items="existingItems"
ng-model="selectedItem"
filter-by="['ref', 'subject']"
project="project"
item-type="{{ objType }}"
reset-on-change="true"
item-disabled="isDisabledExisting"
)
button.button-green.add-existing-button(

View File

@ -1,17 +1,24 @@
a.assign-issue-button.button-gray.is-editable(
a.assign-issue-button.button-gray.is-editable.button-unset(
href=""
tg-check-permission="add_us"
title="{{ 'ISSUES.ACTION_ASSIGN_SPRINT' | translate }}"
ng-class="{'button-set': issue.milestone}"
ng-show="!issue.milestone"
tg-check-permission="modify_issue"
title="{{ 'ISSUES.ACTION_ATTACH_SPRINT' | translate }}"
)
tg-svg(svg-icon="icon-promote")
tg-svg(svg-icon="icon-attach")
a.assign-issue-button.button-gray.is-editable.button-set(
href=""
ng-show="issue.milestone"
tg-check-permission="modify_issue"
title="{{ 'ISSUES.ACTION_DETACH_SPRINT' | translate }}"
)
tg-svg(svg-icon="icon-detach")
.lightbox.lightbox-assign-sprint-to-issue
tg-lightbox-close
div.lightbox-assign-related-sprint
h2.title {{ 'ISSUES.ACTION_ASSIGN_SPRINT' | translate }}
h2.title {{ 'ISSUES.ACTION_ATTACH_SPRINT' | translate }}
tg-search-list(
label="{{ 'ISSUES.CHOOSE_SPRINT' | translate }}"
placeholder="{{ 'ISSUES.FILTER_SPRINTS' | translate }}"
@ -26,4 +33,5 @@ a.assign-issue-button.button-gray.is-editable(
ng-click="saveIssueToSprint(selectedSprint, $event)"
tg-loading="vm.loading"
translate="COMMON.SAVE"
ng-disabled="!selectedSprint"
)

View File

@ -200,6 +200,9 @@
border-color: $yellow-green;
}
}
.assign-issue-button.button-set:hover {
background: $red-light;
}
.item-block,
.item-unblock {
display: none;

View File

@ -467,5 +467,15 @@
<path
d="M106-.3a12.5 12.5 0 0 0-12.4 12.5v91H12.3A12.5 12.5 0 0 0-.2 115.5v272.2a12.5 12.5 0 0 0 12.5 12.5H294a12.5 12.5 0 0 0 12.4-12.5v-91h81.3a12.5 12.5 0 0 0 12.5-12.4v-272A12.5 12.5 0 0 0 387.7-.4H106zm12.6 25h256.6V272h-68.8V115.5A12.5 12.5 0 0 0 294 103H118.5V24.8zM24.8 128h68.8v156.4A12.5 12.5 0 0 0 106 297h175.4v78.3H24.8V128zm93.8 0h162.8v144H118.6V128z"/>
</symbol>
<symbol id="icon-attach" viewBox="0 0 21 20" fill="none">
<path d="M17.7672 6.98451C17.8732 7.02661 17.9509 7.11965 17.974 7.23136C17.9971 7.34341 17.9625 7.45953 17.882 7.54034L12.4693 12.9534L7.52218 17.9005C7.45801 17.9647 7.37142 18 7.28212 18C7.25938 18 7.23629 17.9976 7.21354 17.9929C7.10149 17.9698 7.00879 17.8924 6.96669 17.7861C5.87573 15.047 6.7762 12.317 7.07093 11.5599L2.75159 6.48266C2.28404 6.52069 1.0705 6.53258 0.12827 5.7849C0.0525516 5.7248 0.00603411 5.63482 0.000601404 5.53839C-0.00517085 5.44128 0.0308208 5.34689 0.0994087 5.27864L5.27881 0.0992359C5.34672 0.0309875 5.44146 -0.00194827 5.53721 8.89983e-05C5.6333 0.0055217 5.7226 0.0513602 5.78338 0.126399C5.82073 0.172577 6.65736 1.23331 6.4808 2.78435L11.5329 7.0823C12.2758 6.75871 14.856 5.82463 17.7672 6.98451Z" transform="translate(1.5 1.48999)" stroke="white" stroke-width="1.1"/>
<path d="M0 0V6.63158" transform="translate(14.228 14.2179) rotate(-45)" stroke="white" stroke-width="1.1" stroke-linecap="round"/>
</symbol>
<symbol id="icon-detach" viewBox="0 0 21 20" fill="none">
<path d="M6.13308 11.2098L6.99441 12.2223C6.62356 13.3025 5.95632 15.9041 7.00539 18.5387L7.51687 18.3361L7.00588 18.5396C7.11672 18.8184 7.35964 19.0207 7.65115 19.0813C7.71072 19.0937 7.77151 19.1001 7.8323 19.1001C8.06643 19.1001 8.29324 19.0075 8.4612 18.8394L18.8211 8.47921C19.0327 8.26656 19.1231 7.96285 19.0628 7.67037C19.0025 7.37826 18.7993 7.13448 18.5208 7.02352C15.7425 5.91671 13.2537 6.59298 12.1853 6.9974L11.1912 6.15157L10.4109 6.93197L11.9881 8.27364L12.3025 8.13656C12.9673 7.84701 15.2266 7.02999 17.812 7.9327L7.91555 17.8294C7.08254 15.3938 7.86282 13.0052 8.13357 12.3094L8.25271 12.0035L6.91335 10.4294L6.13308 11.2098Z" transform="translate(1 0.25)" fill="white"/>
<path d="M8.11843 4.98165L6.4512 3.56332L6.4844 3.27218C6.55984 2.61007 6.41921 2.0516 6.25393 1.65084C6.17947 1.4703 6.10085 1.3243 6.03738 1.21859L1.21389 6.04208C1.95583 6.51632 2.85769 6.51693 3.2571 6.48446L3.53787 6.46163L4.96316 8.13705L4.18288 8.91732L3.05837 7.59542C2.4258 7.60873 1.27956 7.51412 0.336449 6.76571C0.137719 6.608 0.0161365 6.37277 0.0014881 6.1202C-0.0131603 5.86666 0.0808338 5.61898 0.261498 5.43881L5.43948 0.260828C5.62722 0.072351 5.8738 -0.00455333 6.0989 0.000207414L6.10867 0.000451555L6.11843 0.000939836C6.20779 0.00606679 6.29495 0.0244994 6.37698 0.0548949C6.5259 0.110071 6.65896 0.204553 6.76101 0.33053C6.82644 0.411218 7.67312 1.48995 7.6011 3.09738L8.8987 4.20138L8.11843 4.98165Z" transform="translate(1 0.25)" fill="white"/>
<path d="M0 0V6.63158" transform="translate(14.2783 13.528) rotate(-45)" stroke="white" stroke-width="1.1" stroke-linecap="round"/>
<line y1="-0.5" x2="21" y2="-0.5" transform="translate(15.5 0.5) rotate(135)" stroke="white"/>
</symbol>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 73 KiB