Allow user save own filters

stable
Jesús Espino 2014-08-20 14:27:25 +02:00
parent b1bc25561c
commit 00a8d9feb3
6 changed files with 232 additions and 118 deletions

View File

@ -76,6 +76,10 @@ class FiltersMixin
location = if load then @location else @location.noreload(@scope)
location.search(name, value)
replaceAllFilters: (filters, load=false) ->
location = if load then @location else @location.noreload(@scope)
location.search(filters)
unselectFilter: (name, value, load=false) ->
params = @location.search()

View File

@ -108,115 +108,97 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
filters = _.pick(@location.search(), name)
return filters[name]
loadMyFilters: ->
deferred = @q.defer()
promise = @rs.issues.getMyFilters(@scope.projectId)
promise.then (filters) ->
result = _.map filters, (value, key) =>
obj = {
id: key,
name: key,
type: "myFilters"
}
obj.selected = false
return obj
deferred.resolve(result)
return deferred.promise
markSelectedFilters: (filters, urlfilters) ->
# Build selected filters (from url) fast lookup data structure
searchdata = {}
for name, value of _.omit(urlfilters, "page", "orderBy")
if not searchdata[name]?
searchdata[name] = {}
for val in "#{value}".split(",")
searchdata[name][val] = true
isSelected = (type, id) ->
if searchdata[type]? and searchdata[type][id]
return true
return false
for key, value of filters
for obj in value
obj.selected = if isSelected(obj.type, obj.id) then true else undefined
loadFilters: ->
# This function is executed only once when page is loads and
# it needs create all filters structure and know that
# filters are selected from url params.
deferred = @q.defer()
urlfilters = @.getUrlFilters()
if urlfilters.subject
@scope.filtersSubject = urlfilters.subject
return @rs.issues.filtersData(@scope.projectId).then (data) =>
# Build selected filters (from url) fast lookup data structure
searchdata = {}
@.loadMyFilters().then (myFilters) =>
@scope.filters.myFilters = myFilters
@rs.issues.filtersData(@scope.projectId).then (data) =>
usersFiltersFormat = (users, type, unknownOption) =>
reformatedUsers = _.map users, (t) =>
return {
id: t[0],
count: t[1],
type: type
name: if t[0] then @scope.usersById[t[0]].full_name_display else unknownOption
}
unknownItem = _.remove(reformatedUsers, (u) -> not u.id)
reformatedUsers = _.sortBy(reformatedUsers, (u) -> u.name.toUpperCase())
if unknownItem.length > 0
reformatedUsers.unshift(unknownItem[0])
return reformatedUsers
for name, value of _.omit(urlfilters, "page", "orderBy")
# if name == "page" or name == "orderBy"
# continue
if not searchdata[name]?
searchdata[name] = {}
choicesFiltersFormat = (choices, type, byIdObject) =>
_.map choices, (t) ->
return {
id: t[0],
name: byIdObject[t[0]].name,
color: byIdObject[t[0]].color,
count: t[1],
type: type}
for val in "#{value}".split(",")
searchdata[name][val] = true
tagsFilterFormat = (tags) =>
return _.map tags, (t) =>
return {
id: t[0],
name: t[0],
color: @scope.project.tags_colors[t[0]],
count: t[1],
type: "tags"
}
isSelected = (type, id) ->
if searchdata[type]? and searchdata[type][id]
return true
return false
# Build filters data structure
@scope.filters.statuses = choicesFiltersFormat data.statuses, "statuses", @scope.issueStatusById
@scope.filters.severities = choicesFiltersFormat data.severities, "severities", @scope.severityById
@scope.filters.priorities = choicesFiltersFormat data.priorities, "priorities", @scope.priorityById
@scope.filters.assignedTo = usersFiltersFormat data.assigned_to, "assignedTo", "Unassigned"
@scope.filters.createdBy = usersFiltersFormat data.created_by, "createdBy", "Unknown"
@scope.filters.types = choicesFiltersFormat data.types, "types", @scope.issueTypeById
@scope.filters.tags = tagsFilterFormat data.tags
usersFiltersFormat = (users, type, unknownOption) =>
reformatedUsers = _.map users, (t) =>
obj = {
id: t[0],
count: t[1],
type: type
}
if t[0]
obj.name = @scope.usersById[t[0]].full_name_display
else
obj.name = unknownOption
@.markSelectedFilters(@scope.filters, urlfilters)
obj.selected = true if isSelected(type, obj.id)
return obj
unknownItem = _.remove(reformatedUsers, (u) -> not u.id)
reformatedUsers = _.sortBy(reformatedUsers, (u) -> u.name.toUpperCase())
if unknownItem.length > 0
reformatedUsers.unshift(unknownItem[0])
return reformatedUsers
# Build filters data structure
@scope.filters.statuses = _.map data.statuses, (t) =>
obj = {
id: t[0],
name: @scope.issueStatusById[t[0]].name,
color: @scope.issueStatusById[t[0]].color,
count: t[1],
type: "statuses"}
obj.selected = true if isSelected("statuses", obj.id)
return obj
@scope.filters.severities = _.map data.severities, (t) =>
obj = {
id: t[0],
name: @scope.severityById[t[0]].name,
color: @scope.severityById[t[0]].color,
count: t[1],
type: "severities"
}
obj.selected = true if isSelected("severities", obj.id)
return obj
@scope.filters.priorities = _.map data.priorities, (t) =>
obj = {
id: t[0],
name: @scope.priorityById[t[0]].name,
color: @scope.priorityById[t[0]].color
count: t[1],
type: "priorities"
}
obj.selected = true if isSelected("priorities", obj.id)
return obj
@scope.filters.assignedTo = usersFiltersFormat data.assigned_to, "assignedTo", "Unassigned"
@scope.filters.createdBy = usersFiltersFormat data.created_by, "createdBy", "Unknown"
@scope.filters.tags = _.map data.tags, (t) =>
obj = {
id: t[0],
name: t[0],
color: @scope.project.tags_colors[t[0]],
count: t[1],
type: "tags"
}
obj.selected = true if isSelected("tags", obj.id)
return obj
@scope.filters.types = _.map data.types, (t) =>
obj = {
id: t[0],
name: @scope.issueTypeById[t[0]].name,
color: @scope.issueTypeById[t[0]].color
count: t[1],
type: "types"
}
obj.selected = true if isSelected("types", obj.id)
return obj
@rootscope.$broadcast("filters:loaded", @scope.filters)
return data
@rootscope.$broadcast("filters:loaded", @scope.filters)
deferred.resolve()
return deferred.promise
loadIssues: ->
@scope.urlFilters = @.getUrlFilters()
@ -263,6 +245,22 @@ class IssuesController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
.then(=> @.loadFilters())
.then(=> @.loadIssues())
saveCurrentFiltersTo: (newFilter) ->
deferred = @q.defer()
@rs.issues.getMyFilters(@scope.projectId).then (filters) =>
filters[newFilter] = @location.search()
@rs.issues.storeMyFilters(@scope.projectId, filters).then =>
deferred.resolve()
return deferred.promise
deleteMyFilter: (filter) ->
deferred = @q.defer()
@rs.issues.getMyFilters(@scope.projectId).then (filters) =>
delete filters[filter]
@rs.issues.storeMyFilters(@scope.projectId, filters).then =>
deferred.resolve()
return deferred.promise
# Functions used from templates
addNewIssue: ->
@rootscope.$broadcast("issueform:new", @scope.project)
@ -440,29 +438,26 @@ IssuesDirective = ($log, $location) ->
## Issues Filters Directive
#############################################################################
IssuesFiltersDirective = ($log, $location) ->
IssuesFiltersDirective = ($log, $location, $rs) ->
template = _.template("""
<% _.each(filters, function(f) { %>
<% if (f.selected) { %>
<a class="single-filter active"
data-type="<%= f.type %>"
data-id="<%= f.id %>">
<span class="name">
<%- f.name %>
</span>
<span class="number"><%- f.count %></span>
</a>
<% } else { %>
<% if (!f.selected) { %>
<a class="single-filter"
data-type="<%= f.type %>"
data-id="<%= f.id %>">
<span class="name" <% if (f.color){ %>style="border-left: 3px solid <%- f.color %>;"<% } %>>
<%- f.name %>
</span>
<% if (f.count){ %>
<span class="number"><%- f.count %></span>
<% } %>
<% if (f.type == "myFilters"){ %>
<span class="icon icon-delete"></span>
<% } %>
</a>
<% } %>
<% }) %>
<input class="hidden my-filter-name" type="text" placeholder="filter name" />
""")
templateSelected = _.template("""
@ -501,17 +496,34 @@ IssuesFiltersDirective = ($log, $location) ->
for val in values
selectedFilters.push(val) if val.selected
renderSelectedFilters()
renderSelectedFilters(selectedFilters)
renderSelectedFilters = ->
renderSelectedFilters = (selectedFilters) ->
html = templateSelected({filters:selectedFilters})
$el.find(".filters-applied").html(html)
if selectedFilters.length > 0
$el.find(".save-filters").show()
else
$el.find(".save-filters").hide()
renderFilters = (filters) ->
html = template({filters:filters})
$el.find(".filter-list").html(html)
toggleFilterSelection = (type, id) ->
if type == "myFilters"
$rs.issues.getMyFilters($scope.projectId).then (data) ->
myFilters = data
filters = myFilters[id]
filters.page = 1
$ctrl.replaceAllFilters(filters)
$ctrl.storeFilters()
$ctrl.loadIssues()
$ctrl.markSelectedFilters($scope.filters, filters)
initializeSelectedFilters($scope.filters)
return null
filters = $scope.filters[type]
filter = _.find(filters, {id:id})
filter.selected = (not filter.selected)
@ -580,18 +592,57 @@ IssuesFiltersDirective = ($log, $location) ->
$el.on "click", ".filter-list .single-filter", (event) ->
event.preventDefault()
target = angular.element(event.currentTarget)
if target.hasClass("active")
target.removeClass("active")
else
target.addClass("active")
target.toggleClass("active")
id = target.data("id") or null
type = target.data("type")
# A saved filter can't be active
if type == "myFilters"
target.removeClass("active")
toggleFilterSelection(type, id)
$el.on "click", ".filter-list .single-filter .icon-delete", (event) ->
event.preventDefault()
event.stopPropagation()
target = angular.element(event.currentTarget)
$ctrl.deleteMyFilter(target.parent().data('id')).then ->
$ctrl.loadMyFilters().then (filters) ->
$scope.filters.myFilters = filters
renderFilters($scope.filters.myFilters)
$el.on "click", ".save-filters", (event) ->
event.preventDefault()
renderFilters($scope.filters["myFilters"])
showFilters("My filters", "myFilters")
$el.find('.save-filters').hide()
$el.find('.my-filter-name').show()
$el.find('.my-filter-name').focus()
$el.on "keyup", ".my-filter-name", (event) ->
event.preventDefault()
if event.keyCode == 13
target = angular.element(event.currentTarget)
newFilter = target.val()
$ctrl.saveCurrentFiltersTo(newFilter).then ->
$ctrl.loadMyFilters().then (filters) ->
$scope.filters.myFilters = filters
currentfilterstype = $el.find("h2 a.subfilter span.title").prop('data-type')
if currentfilterstype == "myFilters"
renderFilters($scope.filters.myFilters)
$el.find('.my-filter-name').hide()
$el.find('.save-filters').show()
else if event.keyCode == 27
$el.find('.my-filter-name').val('')
$el.find('.my-filter-name').hide()
$el.find('.save-filters').show()
return {link:link}
module.directive("tgIssuesFilters", ["$log", "$tgLocation", IssuesFiltersDirective])
module.directive("tgIssuesFilters", ["$log", "$tgLocation", "$tgResources", IssuesFiltersDirective])
module.directive("tgIssues", ["$log", "$tgLocation", IssuesDirective])

View File

@ -71,6 +71,7 @@ urls = {
"users-change-password-from-recovery": "/api/v1/users/change_password_from_recovery"
"users-change-password": "/api/v1/users/change_password"
"users-change-email": "/api/v1/users/change_email"
"user-storage": "/api/v1/user-storage"
"resolver": "/api/v1/resolver"
"userstory-statuses": "/api/v1/userstory-statuses"
"points": "/api/v1/points"

View File

@ -24,10 +24,11 @@ taiga = @.taiga
generateHash = taiga.generateHash
resourceProvider = ($repo, $http, $urls, $storage) ->
resourceProvider = ($repo, $http, $urls, $storage, $q) ->
service = {}
hashSuffix = "issues-queryparams"
filtersHashSuffix = "issues-filters"
myFiltersHashSuffix = "issues-my-filters"
service.get = (projectId, issueId) ->
params = service.getQueryParams(projectId)
@ -79,9 +80,46 @@ resourceProvider = ($repo, $http, $urls, $storage) ->
hash = generateHash([projectSlug, ns])
return $storage.get(hash) or {}
service.storeMyFilters = (projectId, myFilters) ->
deferred = $q.defer()
url = $urls.resolve("user-storage")
ns = "#{projectId}:#{myFiltersHashSuffix}"
hash = generateHash([projectId, ns])
if _.isEmpty(myFilters)
promise = $http.delete("#{url}/#{hash}", {key: hash, value:myFilters})
promise.then ->
deferred.resolve()
promise.then null, ->
deferred.reject()
else
promise = $http.put("#{url}/#{hash}", {key: hash, value:myFilters})
promise.then (data) ->
deferred.resolve()
promise.then null, (data) ->
innerPromise = $http.post("#{url}", {key: hash, value:myFilters})
innerPromise.then ->
deferred.resolve()
innerPromise.then null, ->
deferred.reject()
return deferred.promise
service.getMyFilters = (projectId) ->
deferred = $q.defer()
url = $urls.resolve("user-storage")
ns = "#{projectId}:#{myFiltersHashSuffix}"
hash = generateHash([projectId, ns])
promise = $http.get("#{url}/#{hash}")
promise.then (data) ->
deferred.resolve(data.data.value)
promise.then null, (data) ->
deferred.resolve({})
return deferred.promise
return (instance) ->
instance.issues = service
module = angular.module("taigaResources")
module.factory("$tgIssuesResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", "$tgStorage", resourceProvider])
module.factory("$tgIssuesResourcesProvider", ["$tgRepo", "$tgHttp", "$tgUrls", "$tgStorage", "$q", resourceProvider])

View File

@ -8,6 +8,7 @@ section.filters
a.icon.icon-search(href="", title="search")
div.filters-step-cat
div.filters-applied
a.hidden.button.save-filters(href="", title="save", ng-class="{hidden: filters.length}") save
h2
a.hidden.subfilter(href="", title="cat-name")
span.title status
@ -41,5 +42,9 @@ section.filters
a(href="", title="Created by", data-type="createdBy")
span.title Created by
span.icon.icon-arrow-right
li
a(href="", title="My filters", data-type="myFilters")
span.title My filters
span.icon.icon-arrow-right
div.filter-list.hidden

View File

@ -61,6 +61,21 @@
}
}
.filters-step-cat {
.save-filters {
float: right;
padding: 8px 0px;
}
.my-filter-name {
background: $grayer;
color: $whitish;
width: 100%;
@include placeholder {
color: $gray-light;
}
}
}
.filters-cats {
margin-top: 2rem;
li {