Add analytics service and some additional tracking events.
parent
739dea8eae
commit
51dc862e5e
|
@ -186,13 +186,16 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
||||||
linewidth: "The subject must have a maximum size of %s"
|
linewidth: "The subject must have a maximum size of %s"
|
||||||
})
|
})
|
||||||
|
|
||||||
init = ($log, $i18n, $config, $rootscope, $auth, $events) ->
|
init = ($log, $i18n, $config, $rootscope, $auth, $events, $analytics) ->
|
||||||
$i18n.initialize($config.get("defaultLanguage"))
|
$i18n.initialize($config.get("defaultLanguage"))
|
||||||
$log.debug("Initialize application")
|
$log.debug("Initialize application")
|
||||||
|
|
||||||
if $auth.isAuthenticated()
|
if $auth.isAuthenticated()
|
||||||
$events.setupConnection()
|
$events.setupConnection()
|
||||||
|
|
||||||
|
$analytics.initialize()
|
||||||
|
|
||||||
|
|
||||||
modules = [
|
modules = [
|
||||||
# Main Global Modules
|
# Main Global Modules
|
||||||
"taigaBase",
|
"taigaBase",
|
||||||
|
@ -244,5 +247,6 @@ module.run([
|
||||||
"$rootScope",
|
"$rootScope",
|
||||||
"$tgAuth",
|
"$tgAuth",
|
||||||
"$tgEvents",
|
"$tgEvents",
|
||||||
|
"$tgAnalytics",
|
||||||
init
|
init
|
||||||
])
|
])
|
||||||
|
|
|
@ -214,7 +214,7 @@ module.directive("tgLogin", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgConfig"
|
||||||
## Register Directive
|
## Register Directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
RegisterDirective = ($auth, $confirm, $location, $navUrls, $config) ->
|
RegisterDirective = ($auth, $confirm, $location, $navUrls, $config, $analytics) ->
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
if not $config.get("publicRegisterEnabled")
|
if not $config.get("publicRegisterEnabled")
|
||||||
$location.path($navUrls.resolve("not-found"))
|
$location.path($navUrls.resolve("not-found"))
|
||||||
|
@ -224,6 +224,7 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config) ->
|
||||||
form = $el.find("form").checksley()
|
form = $el.find("form").checksley()
|
||||||
|
|
||||||
onSuccessSubmit = (response) ->
|
onSuccessSubmit = (response) ->
|
||||||
|
$analytics.trackEvent("auth", "register", "user registration", 1)
|
||||||
$confirm.notify("success", "Our Oompa Loompas are happy, welcome to Taiga.") #TODO: i18n
|
$confirm.notify("success", "Our Oompa Loompas are happy, welcome to Taiga.") #TODO: i18n
|
||||||
$location.path($navUrls.resolve("home"))
|
$location.path($navUrls.resolve("home"))
|
||||||
|
|
||||||
|
@ -251,7 +252,7 @@ RegisterDirective = ($auth, $confirm, $location, $navUrls, $config) ->
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
module.directive("tgRegister", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgNavUrls", "$tgConfig",
|
module.directive("tgRegister", ["$tgAuth", "$tgConfirm", "$tgLocation", "$tgNavUrls", "$tgConfig",
|
||||||
RegisterDirective])
|
"$tgAnalytics", RegisterDirective])
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Forgot Password Directive
|
## Forgot Password Directive
|
||||||
|
@ -342,7 +343,7 @@ module.directive("tgChangePasswordFromRecovery", ["$tgAuth", "$tgConfirm", "$tgL
|
||||||
## Invitation
|
## Invitation
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
InvitationDirective = ($auth, $confirm, $location, $params, $navUrls) ->
|
InvitationDirective = ($auth, $confirm, $location, $params, $navUrls, $analytics) ->
|
||||||
link = ($scope, $el, $attrs) ->
|
link = ($scope, $el, $attrs) ->
|
||||||
token = $params.token
|
token = $params.token
|
||||||
|
|
||||||
|
@ -360,6 +361,7 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls) ->
|
||||||
loginForm = $el.find("form.login-form").checksley()
|
loginForm = $el.find("form.login-form").checksley()
|
||||||
|
|
||||||
onSuccessSubmitLogin = (response) ->
|
onSuccessSubmitLogin = (response) ->
|
||||||
|
$analytics.trackEvent("auth", "invitationAccept", "invitation accept with existing user", 1)
|
||||||
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
|
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
|
||||||
$confirm.notify("success", "You've successfully joined this project",
|
$confirm.notify("success", "You've successfully joined this project",
|
||||||
"Welcome to #{$scope.invitation.project_name}")
|
"Welcome to #{$scope.invitation.project_name}")
|
||||||
|
@ -388,6 +390,7 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls) ->
|
||||||
registerForm = $el.find("form.register-form").checksley()
|
registerForm = $el.find("form.register-form").checksley()
|
||||||
|
|
||||||
onSuccessSubmitRegister = (response) ->
|
onSuccessSubmitRegister = (response) ->
|
||||||
|
$analytics.trackEvent("auth", "invitationAccept", "invitation accept with new user", 1)
|
||||||
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
|
$location.path($navUrls.resolve("project", {project: $scope.invitation.project_slug}))
|
||||||
$confirm.notify("success", "You've successfully joined this project",
|
$confirm.notify("success", "You've successfully joined this project",
|
||||||
"Welcome to #{$scope.invitation.project_name}")
|
"Welcome to #{$scope.invitation.project_name}")
|
||||||
|
@ -414,7 +417,7 @@ InvitationDirective = ($auth, $confirm, $location, $params, $navUrls) ->
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
module.directive("tgInvitation", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams",
|
module.directive("tgInvitation", ["$tgAuth", "$tgConfirm", "$tgLocation", "$routeParams",
|
||||||
"$tgNavUrls", InvitationDirective])
|
"$tgNavUrls", "$tgAnalytics", InvitationDirective])
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
## Change Email
|
## Change Email
|
||||||
|
|
|
@ -33,6 +33,7 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
|
||||||
link = ($scope, $el, attrs) ->
|
link = ($scope, $el, attrs) ->
|
||||||
hasErrors = false
|
hasErrors = false
|
||||||
createSprint = true
|
createSprint = true
|
||||||
|
|
||||||
$scope.sprint = {
|
$scope.sprint = {
|
||||||
project: null
|
project: null
|
||||||
name: null
|
name: null
|
||||||
|
@ -41,35 +42,41 @@ CreateEditSprint = ($repo, $confirm, $rs, $rootscope, lightboxService, $loading)
|
||||||
}
|
}
|
||||||
|
|
||||||
submit = (event) ->
|
submit = (event) ->
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
form = $el.find("form").checksley()
|
form = $el.find("form").checksley()
|
||||||
|
|
||||||
if not form.validate()
|
if not form.validate()
|
||||||
hasErrors = true
|
hasErrors = true
|
||||||
$el.find(".last-sprint-name").addClass("disappear")
|
$el.find(".last-sprint-name").addClass("disappear")
|
||||||
return
|
return
|
||||||
hasErrors = false
|
|
||||||
|
|
||||||
|
hasErrors = false
|
||||||
newSprint = angular.copy($scope.sprint)
|
newSprint = angular.copy($scope.sprint)
|
||||||
|
broadcastEvent = null
|
||||||
|
|
||||||
if createSprint
|
if createSprint
|
||||||
newSprint.estimated_start = moment(newSprint.estimated_start).format("YYYY-MM-DD")
|
newSprint.estimated_start = moment(newSprint.estimated_start).format("YYYY-MM-DD")
|
||||||
newSprint.estimated_finish = moment(newSprint.estimated_finish).format("YYYY-MM-DD")
|
newSprint.estimated_finish = moment(newSprint.estimated_finish).format("YYYY-MM-DD")
|
||||||
promise = $repo.create("milestones", newSprint)
|
promise = $repo.create("milestones", newSprint)
|
||||||
|
broadcastEvent = "sprintform:create:success"
|
||||||
else
|
else
|
||||||
newSprint.setAttr("estimated_start", moment(newSprint.estimated_start).format("YYYY-MM-DD"))
|
newSprint.setAttr("estimated_start", moment(newSprint.estimated_start).format("YYYY-MM-DD"))
|
||||||
newSprint.setAttr("estimated_finish", moment(newSprint.estimated_finish).format("YYYY-MM-DD"))
|
newSprint.setAttr("estimated_finish", moment(newSprint.estimated_finish).format("YYYY-MM-DD"))
|
||||||
promise = $repo.save(newSprint)
|
promise = $repo.save(newSprint)
|
||||||
|
broadcastEvent = "sprintform:edit:success"
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
$loading.start(target)
|
$loading.start(target)
|
||||||
|
|
||||||
promise.then (data) ->
|
promise.then (data) ->
|
||||||
$loading.finish(target)
|
$loading.finish(target)
|
||||||
$scope.sprintsCounter += 1 if createSprint
|
$scope.sprintsCounter += 1 if createSprint
|
||||||
|
$rootscope.$broadcast(broadcastEvent, data)
|
||||||
|
|
||||||
lightboxService.close($el)
|
lightboxService.close($el)
|
||||||
$rootscope.$broadcast("sprintform:create:success", data)
|
|
||||||
|
|
||||||
promise.then null, (data) ->
|
promise.then null, (data) ->
|
||||||
$loading.finish(target)
|
$loading.finish(target)
|
||||||
|
|
||||||
form.setErrors(data)
|
form.setErrors(data)
|
||||||
if data._error_message
|
if data._error_message
|
||||||
$confirm.notify("light-error", data._error_message)
|
$confirm.notify("light-error", data._error_message)
|
||||||
|
|
|
@ -47,16 +47,19 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
"$tgNavUrls",
|
"$tgNavUrls",
|
||||||
"$tgEvents",
|
"$tgEvents",
|
||||||
|
"$tgAnalytics",
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q,
|
||||||
@location, @appTitle, @navUrls, @events, tgLoader) ->
|
@location, @appTitle, @navUrls, @events, @analytics, tgLoader) ->
|
||||||
_.bindAll(@)
|
_.bindAll(@)
|
||||||
|
|
||||||
@scope.sectionName = "Backlog"
|
@scope.sectionName = "Backlog"
|
||||||
@showTags = false
|
@showTags = false
|
||||||
|
|
||||||
|
@.initializeEventHandlers()
|
||||||
|
|
||||||
promise = @.loadInitialData()
|
promise = @.loadInitialData()
|
||||||
|
|
||||||
# On Success
|
# On Success
|
||||||
|
@ -70,12 +73,6 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
|
|
||||||
tgLoader.pageLoaded()
|
tgLoader.pageLoaded()
|
||||||
|
|
||||||
# $(".backlog, .sidebar").mCustomScrollbar({
|
|
||||||
# theme: 'minimal-dark'
|
|
||||||
# scrollInertia: 0
|
|
||||||
# axis: 'y'
|
|
||||||
# })
|
|
||||||
|
|
||||||
# On Error
|
# On Error
|
||||||
promise.then null, (xhr) =>
|
promise.then null, (xhr) =>
|
||||||
if xhr and xhr.status == 404
|
if xhr and xhr.status == 404
|
||||||
|
@ -83,16 +80,33 @@ class BacklogController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.F
|
||||||
@location.replace()
|
@location.replace()
|
||||||
return @q.reject(xhr)
|
return @q.reject(xhr)
|
||||||
|
|
||||||
@scope.$on("usform:bulk:success", @.loadUserstories)
|
initializeEventHandlers: ->
|
||||||
@scope.$on("sprintform:create:success", @.loadSprints)
|
@scope.$on "usform:bulk:success", =>
|
||||||
@scope.$on("sprintform:create:success", @.loadProjectStats)
|
@.loadUserstories()
|
||||||
@scope.$on("sprintform:remove:success", @.loadSprints)
|
@.loadProjectStats()
|
||||||
@scope.$on("sprintform:remove:success", @.loadProjectStats)
|
@analytics.trackEvent("userstory", "create", "bulk create userstory on backlog", 1)
|
||||||
@scope.$on("sprintform:remove:success", @.loadUserstories)
|
|
||||||
@scope.$on("usform:new:success", @.loadUserstories)
|
@scope.$on "sprintform:create:success", =>
|
||||||
@scope.$on("usform:edit:success", @.loadUserstories)
|
@.loadSprints()
|
||||||
@scope.$on("usform:new:success", @.loadProjectStats)
|
@.loadProjectStats()
|
||||||
@scope.$on("usform:bulk:success", @.loadProjectStats)
|
@analytics.trackEvent("sprint", "create", "create sprint on backlog", 1)
|
||||||
|
|
||||||
|
@scope.$on "usform:new:success", =>
|
||||||
|
@.loadUserstories()
|
||||||
|
@.loadProjectStats()
|
||||||
|
@analytics.trackEvent("userstory", "create", "create userstory on backlog", 1)
|
||||||
|
|
||||||
|
@scope.$on "sprintform:edit:success", =>
|
||||||
|
@.loadProjectStats()
|
||||||
|
|
||||||
|
@scope.$on "sprintform:remove:success", =>
|
||||||
|
@.loadSprints()
|
||||||
|
@.loadProjectStats()
|
||||||
|
@.loadUserstories()
|
||||||
|
|
||||||
|
@scope.$on "usform:edit:success", =>
|
||||||
|
@.loadUserstories()
|
||||||
|
|
||||||
@scope.$on("sprint:us:move", @.moveUs)
|
@scope.$on("sprint:us:move", @.moveUs)
|
||||||
@scope.$on("sprint:us:moved", @.loadSprints)
|
@scope.$on("sprint:us:moved", @.loadSprints)
|
||||||
@scope.$on("sprint:us:moved", @.loadProjectStats)
|
@scope.$on("sprint:us:moved", @.loadProjectStats)
|
||||||
|
|
|
@ -0,0 +1,83 @@
|
||||||
|
###
|
||||||
|
# Copyright (C) 2014 Andrey Antukh <niwi@niwi.be>
|
||||||
|
# Copyright (C) 2014 Jesús Espino Garcia <jespinog@gmail.com>
|
||||||
|
# Copyright (C) 2014 David Barragán Merino <bameda@dbarragan.com>
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as
|
||||||
|
# published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
#
|
||||||
|
# File: modules/common/analytics.coffee
|
||||||
|
###
|
||||||
|
|
||||||
|
taiga = @.taiga
|
||||||
|
module = angular.module("taigaCommon")
|
||||||
|
|
||||||
|
|
||||||
|
class AnalyticsService extends taiga.Service
|
||||||
|
@.$inject = ["$rootScope", "$log", "$tgConfig", "$window", "$document", "$location"]
|
||||||
|
|
||||||
|
constructor: (@rootscope, @log, @config, @win, @doc, @location) ->
|
||||||
|
@.initialized = false
|
||||||
|
|
||||||
|
conf = @config.get("analytics", {})
|
||||||
|
|
||||||
|
@.accountId = conf.accountId
|
||||||
|
@.pageEvent = conf.pageEvent or "$routeChangeSuccess"
|
||||||
|
@.trackRoutes = conf.trackRoutes or true
|
||||||
|
@.ignoreFirstPageLoad = conf.ignoreFirstPageLoad or false
|
||||||
|
|
||||||
|
initialize: ->
|
||||||
|
if not @.accountId
|
||||||
|
@log.debug "Analytics: no acount id provided. Disabling."
|
||||||
|
return
|
||||||
|
|
||||||
|
@.injectAnalytics()
|
||||||
|
|
||||||
|
@win.ga("create", @.accountId, "auto")
|
||||||
|
@win.ga("require", "displayfeatures")
|
||||||
|
|
||||||
|
if @.trackRoutes and (not @.ignoreFirstPageLoad)
|
||||||
|
@win.ga("send", "pageview", @.getUrl());
|
||||||
|
|
||||||
|
# activates page tracking
|
||||||
|
if @.trackRoutes
|
||||||
|
@rootscope.$on @.pageEvent, =>
|
||||||
|
@.trackPage(@.getUrl())
|
||||||
|
|
||||||
|
@.initialized = true
|
||||||
|
|
||||||
|
getUrl: ->
|
||||||
|
return @location.path()
|
||||||
|
|
||||||
|
injectAnalytics: ->
|
||||||
|
fn = `(function(i,s,o,g,r,a,m){i["GoogleAnalyticsObject"]=r;i[r]=i[r]||function(){
|
||||||
|
(i[r].q=i[r].q||[]).push(arguments);},i[r].l=1*new Date();a=s.createElement(o),
|
||||||
|
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m);})`
|
||||||
|
fn(window, document, "script", "//www.google-analytics.com/analytics.js", "ga")
|
||||||
|
|
||||||
|
trackPage: (url, title) ->
|
||||||
|
return if not @.initialized
|
||||||
|
|
||||||
|
title = title or @doc[0].title
|
||||||
|
@win.ga("send", "pageview", {
|
||||||
|
"page": url,
|
||||||
|
"title": title
|
||||||
|
})
|
||||||
|
|
||||||
|
trackEvent: (category, action, label, value) ->
|
||||||
|
return if not @.initialized
|
||||||
|
@win.ga("send", "event", category, action, label, value)
|
||||||
|
|
||||||
|
|
||||||
|
module.service("$tgAnalytics", AnalyticsService)
|
||||||
|
|
|
@ -267,7 +267,6 @@ CreateEditUserstoryDirective = ($repo, $model, $rs, $rootScope, lightboxService,
|
||||||
if isNew
|
if isNew
|
||||||
promise = $repo.create("userstories", $scope.us)
|
promise = $repo.create("userstories", $scope.us)
|
||||||
broadcastEvent = "usform:new:success"
|
broadcastEvent = "usform:new:success"
|
||||||
|
|
||||||
else
|
else
|
||||||
promise = $repo.save($scope.us)
|
promise = $repo.save($scope.us)
|
||||||
broadcastEvent = "usform:edit:success"
|
broadcastEvent = "usform:edit:success"
|
||||||
|
@ -315,15 +314,12 @@ CreateBulkUserstoriesDirective = ($repo, $rs, $rootscope, lightboxService, $load
|
||||||
|
|
||||||
$el.on "click", ".button-green", debounce 2000, (event) ->
|
$el.on "click", ".button-green", debounce 2000, (event) ->
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
target = angular.element(event.currentTarget)
|
||||||
|
|
||||||
form = $el.find("form").checksley({
|
form = $el.find("form").checksley({onlyOneErrorElement: true})
|
||||||
onlyOneErrorElement: true
|
|
||||||
})
|
|
||||||
if not form.validate()
|
if not form.validate()
|
||||||
return
|
return
|
||||||
|
|
||||||
target = angular.element(event.currentTarget)
|
|
||||||
|
|
||||||
$loading.start(target)
|
$loading.start(target)
|
||||||
|
|
||||||
promise = $rs.userstories.bulkCreate($scope.new.projectId, $scope.new.statusId, $scope.new.bulk)
|
promise = $rs.userstories.bulkCreate($scope.new.projectId, $scope.new.statusId, $scope.new.bulk)
|
||||||
|
|
|
@ -45,12 +45,15 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
"$tgLocation",
|
"$tgLocation",
|
||||||
"$log",
|
"$log",
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
|
"$tgAnalytics",
|
||||||
"$tgNavUrls"
|
"$tgNavUrls"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appTitle, @navUrls) ->
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||||
|
@log, @appTitle, @analytics, @navUrls) ->
|
||||||
@scope.issueRef = @params.issueref
|
@scope.issueRef = @params.issueref
|
||||||
@scope.sectionName = "Issue Details"
|
@scope.sectionName = "Issue Details"
|
||||||
|
@.initializeEventHandlers()
|
||||||
|
|
||||||
promise = @.loadInitialData()
|
promise = @.loadInitialData()
|
||||||
|
|
||||||
|
@ -65,10 +68,20 @@ class IssueDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@location.replace()
|
@location.replace()
|
||||||
return @q.reject(xhr)
|
return @q.reject(xhr)
|
||||||
|
|
||||||
@scope.$on "attachment:create", => @rootscope.$broadcast("history:reload")
|
|
||||||
@scope.$on "attachment:edit", => @rootscope.$broadcast("history:reload")
|
initializeEventHandlers: ->
|
||||||
@scope.$on "attachment:delete", => @rootscope.$broadcast("history:reload")
|
@scope.$on "attachment:create", =>
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
@analytics.trackEvent("attachment", "create", "create attachment on issue", 1)
|
||||||
|
|
||||||
|
@scope.$on "attachment:edit", =>
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
|
@scope.$on "attachment:delete", =>
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
@scope.$on "promote-issue-to-us:success", =>
|
@scope.$on "promote-issue-to-us:success", =>
|
||||||
|
@analytics.trackEvent("issue", "promoteToUserstory", "promote issue to userstory", 1)
|
||||||
@rootscope.$broadcast("history:reload")
|
@rootscope.$broadcast("history:reload")
|
||||||
@.loadIssue()
|
@.loadIssue()
|
||||||
|
|
||||||
|
|
|
@ -60,15 +60,16 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
"$tgNavUrls",
|
"$tgNavUrls",
|
||||||
"$tgEvents",
|
"$tgEvents",
|
||||||
|
"$tgAnalytics",
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||||
@appTitle, @navUrls, @events, tgLoader) ->
|
@appTitle, @navUrls, @events, @analytics, tgLoader) ->
|
||||||
_.bindAll(@)
|
_.bindAll(@)
|
||||||
@scope.sectionName = "Kanban"
|
@scope.sectionName = "Kanban"
|
||||||
@scope.statusViewModes = {}
|
@scope.statusViewModes = {}
|
||||||
|
@.initializeEventHandlers()
|
||||||
promise = @.loadInitialData()
|
promise = @.loadInitialData()
|
||||||
|
|
||||||
# On Success
|
# On Success
|
||||||
|
@ -83,8 +84,16 @@ class KanbanController extends mixOf(taiga.Controller, taiga.PageMixin, taiga.Fi
|
||||||
@location.replace()
|
@location.replace()
|
||||||
return @q.reject(xhr)
|
return @q.reject(xhr)
|
||||||
|
|
||||||
@scope.$on("usform:new:success", @.loadUserstories)
|
|
||||||
@scope.$on("usform:bulk:success", @.loadUserstories)
|
initializeEventHandlers: ->
|
||||||
|
@scope.$on "usform:new:success", =>
|
||||||
|
@.loadUserstories()
|
||||||
|
@analytics.trackEvent("userstory", "create", "create userstory on kanban", 1)
|
||||||
|
|
||||||
|
@scope.$on "usform:bulk:success", =>
|
||||||
|
@.loadUserstories()
|
||||||
|
@analytics.trackEvent("userstory", "create", "bulk create userstory on kanban", 1)
|
||||||
|
|
||||||
@scope.$on("usform:edit:success", @.loadUserstories)
|
@scope.$on("usform:edit:success", @.loadUserstories)
|
||||||
@scope.$on("assigned-to:added", @.onAssignedToChanged)
|
@scope.$on("assigned-to:added", @.onAssignedToChanged)
|
||||||
@scope.$on("kanban:us:move", @.moveUs)
|
@scope.$on("kanban:us:move", @.moveUs)
|
||||||
|
|
|
@ -25,65 +25,65 @@ debounce = @.taiga.debounce
|
||||||
|
|
||||||
module = angular.module("taigaRelatedTasks", [])
|
module = angular.module("taigaRelatedTasks", [])
|
||||||
|
|
||||||
RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading) ->
|
RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading, $analytics) ->
|
||||||
templateView = _.template("""
|
templateView = _.template("""
|
||||||
<div class="tasks">
|
<div class="tasks">
|
||||||
<div class="task-name">
|
<div class="task-name">
|
||||||
<span class="icon icon-iocaine"></span>
|
<span class="icon icon-iocaine"></span>
|
||||||
<a tg-nav="project-tasks-detail:project=project.slug,ref=task.ref" title="<%- task.ref %> <%- task.subject %>" class="clickable">
|
<a tg-nav="project-tasks-detail:project=project.slug,ref=task.ref" title="<%- task.ref %> <%- task.subject %>" class="clickable">
|
||||||
<span>#<%- task.ref %></span>
|
<span>#<%- task.ref %></span>
|
||||||
<span><%- task.subject %></span>
|
<span><%- task.subject %></span>
|
||||||
</a>
|
|
||||||
<div class="task-settings">
|
|
||||||
<% if(perms.modify_task) { %>
|
|
||||||
<a href="" title="Edit" class="icon icon-edit"></a>
|
|
||||||
<% } %>
|
|
||||||
<% if(perms.delete_task) { %>
|
|
||||||
<a href="" title="Delete" class="icon icon-delete delete-task"></a>
|
|
||||||
<% } %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div tg-related-task-status="task" ng-model="task" class="status">
|
|
||||||
<a href="" title="Status Name" class="task-status">
|
|
||||||
<span class="task-status-bind"></span>
|
|
||||||
<% if(perms.modify_task) { %>
|
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
|
||||||
<% } %>
|
|
||||||
</a>
|
</a>
|
||||||
</div>
|
<div class="task-settings">
|
||||||
<div tg-related-task-assigned-to-inline-edition="task" class="assigned-to">
|
|
||||||
<div title="Assigned to" class="task-assignedto">
|
|
||||||
<figure class="avatar"></figure>
|
|
||||||
<% if(perms.modify_task) { %>
|
<% if(perms.modify_task) { %>
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
<a href="" title="Edit" class="icon icon-edit"></a>
|
||||||
|
<% } %>
|
||||||
|
<% if(perms.delete_task) { %>
|
||||||
|
<a href="" title="Delete" class="icon icon-delete delete-task"></a>
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div tg-related-task-status="task" ng-model="task" class="status">
|
||||||
|
<a href="" title="Status Name" class="task-status">
|
||||||
|
<span class="task-status-bind"></span>
|
||||||
|
<% if(perms.modify_task) { %>
|
||||||
|
<span class="icon icon-arrow-bottom"></span>
|
||||||
|
<% } %>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div tg-related-task-assigned-to-inline-edition="task" class="assigned-to">
|
||||||
|
<div title="Assigned to" class="task-assignedto">
|
||||||
|
<figure class="avatar"></figure>
|
||||||
|
<% if(perms.modify_task) { %>
|
||||||
|
<span class="icon icon-arrow-bottom"></span>
|
||||||
|
<% } %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
templateEdit = _.template("""
|
templateEdit = _.template("""
|
||||||
<div class="tasks">
|
<div class="tasks">
|
||||||
<div class="task-name">
|
<div class="task-name">
|
||||||
<input type="text" value="<%- task.subject %>" placeholder="Type the task subject" />
|
<input type="text" value="<%- task.subject %>" placeholder="Type the task subject" />
|
||||||
<div class="task-settings">
|
<div class="task-settings">
|
||||||
<a href="" title="Save" class="icon icon-floppy"></a>
|
<a href="" title="Save" class="icon icon-floppy"></a>
|
||||||
<a href="" title="Cancel" class="icon icon-delete cancel-edit"></a>
|
<a href="" title="Cancel" class="icon icon-delete cancel-edit"></a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div tg-related-task-status="task" ng-model="task" class="status">
|
</div>
|
||||||
<a href="" title="Status Name" class="task-status">
|
<div tg-related-task-status="task" ng-model="task" class="status">
|
||||||
<span class="task-status-bind"></span>
|
<a href="" title="Status Name" class="task-status">
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
<span class="task-status-bind"></span>
|
||||||
</a>
|
<span class="icon icon-arrow-bottom"></span>
|
||||||
</div>
|
</a>
|
||||||
<div tg-related-task-assigned-to-inline-edition="task" class="assigned-to">
|
</div>
|
||||||
<div title="Assigned to" class="task-assignedto">
|
<div tg-related-task-assigned-to-inline-edition="task" class="assigned-to">
|
||||||
<figure class="avatar"></figure>
|
<div title="Assigned to" class="task-assignedto">
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
<figure class="avatar"></figure>
|
||||||
</div>
|
<span class="icon icon-arrow-bottom"></span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
link = ($scope, $el, $attrs, $model) ->
|
link = ($scope, $el, $attrs, $model) ->
|
||||||
|
@ -168,29 +168,29 @@ RelatedTaskRowDirective = ($repo, $compile, $confirm, $rootscope, $loading) ->
|
||||||
|
|
||||||
module.directive("tgRelatedTaskRow", ["$tgRepo", "$compile", "$tgConfirm", "$rootScope", "$tgLoading", RelatedTaskRowDirective])
|
module.directive("tgRelatedTaskRow", ["$tgRepo", "$compile", "$tgConfirm", "$rootScope", "$tgLoading", RelatedTaskRowDirective])
|
||||||
|
|
||||||
RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading) ->
|
RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading, $analytics) ->
|
||||||
template = _.template("""
|
template = _.template("""
|
||||||
<div class="tasks">
|
<div class="tasks">
|
||||||
<div class="task-name">
|
<div class="task-name">
|
||||||
<input type="text" placeholder="Type the new task subject" />
|
<input type="text" placeholder="Type the new task subject" />
|
||||||
<div class="task-settings">
|
<div class="task-settings">
|
||||||
<a href="" title="Save" class="icon icon-floppy"></a>
|
<a href="" title="Save" class="icon icon-floppy"></a>
|
||||||
<a href="" title="Cancel" class="icon icon-delete cancel-edit"></a>
|
<a href="" title="Cancel" class="icon icon-delete cancel-edit"></a>
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div tg-related-task-status="newTask" ng-model="newTask" class="status" not-auto-save="true">
|
</div>
|
||||||
<a href="" title="Status Name" class="task-status">
|
<div tg-related-task-status="newTask" ng-model="newTask" class="status" not-auto-save="true">
|
||||||
<span class="task-status-bind"></span>
|
<a href="" title="Status Name" class="task-status">
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
<span class="task-status-bind"></span>
|
||||||
</a>
|
<span class="icon icon-arrow-bottom"></span>
|
||||||
</div>
|
</a>
|
||||||
<div tg-related-task-assigned-to-inline-edition="newTask" class="assigned-to" not-auto-save="true">
|
</div>
|
||||||
<div title="Assigned to" class="task-assignedto">
|
<div tg-related-task-assigned-to-inline-edition="newTask" class="assigned-to" not-auto-save="true">
|
||||||
<figure class="avatar"></figure>
|
<div title="Assigned to" class="task-assignedto">
|
||||||
<span class="icon icon-arrow-bottom"></span>
|
<figure class="avatar"></figure>
|
||||||
</div>
|
<span class="icon icon-arrow-bottom"></span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
""")
|
""")
|
||||||
|
|
||||||
newTask = {
|
newTask = {
|
||||||
|
@ -209,6 +209,7 @@ RelatedTaskCreateFormDirective = ($repo, $compile, $confirm, $tgmodel, $loading)
|
||||||
$loading.start($el.find('.task-name'))
|
$loading.start($el.find('.task-name'))
|
||||||
promise = $repo.create("tasks", task)
|
promise = $repo.create("tasks", task)
|
||||||
promise.then ->
|
promise.then ->
|
||||||
|
$analytics.trackEvent("task", "create", "create task on userstory", 1)
|
||||||
$loading.finish($el.find('.task-name'))
|
$loading.finish($el.find('.task-name'))
|
||||||
$scope.$emit("related-tasks:add")
|
$scope.$emit("related-tasks:add")
|
||||||
$confirm.notify("success")
|
$confirm.notify("success")
|
||||||
|
|
|
@ -47,14 +47,16 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
"$tgLocation",
|
"$tgLocation",
|
||||||
"$tgNavUrls"
|
"$tgNavUrls"
|
||||||
"$tgEvents"
|
"$tgEvents"
|
||||||
|
"$tgAnalytics",
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @appTitle, @location, @navUrls,
|
||||||
@events, tgLoader) ->
|
@events, @analytics, tgLoader) ->
|
||||||
_.bindAll(@)
|
_.bindAll(@)
|
||||||
|
|
||||||
@scope.sectionName = "Taskboard"
|
@scope.sectionName = "Taskboard"
|
||||||
|
@.initializeEventHandlers()
|
||||||
|
|
||||||
promise = @.loadInitialData()
|
promise = @.loadInitialData()
|
||||||
|
|
||||||
|
@ -70,10 +72,17 @@ class TaskboardController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@location.replace()
|
@location.replace()
|
||||||
return @q.reject(xhr)
|
return @q.reject(xhr)
|
||||||
|
|
||||||
|
initializeEventHandlers: ->
|
||||||
# TODO: Reload entire taskboard after create/edit tasks seems
|
# TODO: Reload entire taskboard after create/edit tasks seems
|
||||||
# a big overhead. It should be optimized in near future.
|
# a big overhead. It should be optimized in near future.
|
||||||
@scope.$on("taskform:bulk:success", => @.loadTaskboard())
|
@scope.$on "taskform:bulk:success", =>
|
||||||
@scope.$on("taskform:new:success", => @.loadTaskboard())
|
@.loadTaskboard()
|
||||||
|
@analytics.trackEvent("task", "create", "bulk create task on taskboard", 1)
|
||||||
|
|
||||||
|
@scope.$on "taskform:new:success", =>
|
||||||
|
@.loadTaskboard()
|
||||||
|
@analytics.trackEvent("task", "create", "create task on taskboard", 1)
|
||||||
|
|
||||||
@scope.$on("taskform:edit:success", => @.loadTaskboard())
|
@scope.$on("taskform:edit:success", => @.loadTaskboard())
|
||||||
@scope.$on("taskboard:task:move", @.taskMove)
|
@scope.$on("taskboard:task:move", @.taskMove)
|
||||||
|
|
||||||
|
|
|
@ -43,13 +43,15 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
"$log",
|
"$log",
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
"$tgNavUrls",
|
"$tgNavUrls",
|
||||||
|
"$tgAnalytics",
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appTitle, @navUrls,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||||
tgLoader) ->
|
@log, @appTitle, @navUrls, @analytics, tgLoader) ->
|
||||||
@scope.taskRef = @params.taskref
|
@scope.taskRef = @params.taskref
|
||||||
@scope.sectionName = "Task Details"
|
@scope.sectionName = "Task Details"
|
||||||
|
@.initializeEventHandlers()
|
||||||
|
|
||||||
promise = @.loadInitialData()
|
promise = @.loadInitialData()
|
||||||
|
|
||||||
|
@ -63,10 +65,14 @@ class TaskDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@location.replace()
|
@location.replace()
|
||||||
return @q.reject(xhr)
|
return @q.reject(xhr)
|
||||||
|
|
||||||
|
initializeEventHandlers: ->
|
||||||
@scope.$on("attachment:create", => @rootscope.$broadcast("history:reload"))
|
@scope.$on "attachment:create", =>
|
||||||
@scope.$on("attachment:edit", => @rootscope.$broadcast("history:reload"))
|
@analytics.trackEvent("attachment", "create", "create attachment on task", 1)
|
||||||
@scope.$on("attachment:delete", => @rootscope.$broadcast("history:reload"))
|
@rootscope.$broadcast("history:reload")
|
||||||
|
@scope.$on "attachment:edit", =>
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
@scope.$on "attachment:delete", =>
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
|
|
|
@ -44,12 +44,15 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
"$log",
|
"$log",
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
"$tgNavUrls",
|
"$tgNavUrls",
|
||||||
|
"$tgAnalytics",
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @log, @appTitle, @navUrls, tgLoader) ->
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||||
|
@log, @appTitle, @navUrls, @analytics, tgLoader) ->
|
||||||
@scope.issueRef = @params.issueref
|
@scope.issueRef = @params.issueref
|
||||||
@scope.sectionName = "User Story Details"
|
@scope.sectionName = "User Story Details"
|
||||||
|
@.initializeEventHandlers()
|
||||||
|
|
||||||
promise = @.loadInitialData()
|
promise = @.loadInitialData()
|
||||||
|
|
||||||
|
@ -65,9 +68,16 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
@location.replace()
|
@location.replace()
|
||||||
return @q.reject(xhr)
|
return @q.reject(xhr)
|
||||||
|
|
||||||
@scope.$on("attachment:create", => @rootscope.$broadcast("history:reload"))
|
initializeEventHandlers: ->
|
||||||
@scope.$on("attachment:edit", => @rootscope.$broadcast("history:reload"))
|
@scope.$on "attachment:create", =>
|
||||||
@scope.$on("attachment:delete", => @rootscope.$broadcast("history:reload"))
|
@analytics.trackEvent("attachment", "create", "create attachment on userstory", 1)
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
|
@scope.$on "attachment:edit", =>
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
|
@scope.$on "attachment:delete", =>
|
||||||
|
@rootscope.$broadcast("history:reload")
|
||||||
|
|
||||||
loadProject: ->
|
loadProject: ->
|
||||||
return @rs.projects.get(@scope.projectId).then (project) =>
|
return @rs.projects.get(@scope.projectId).then (project) =>
|
||||||
|
@ -133,7 +143,6 @@ class UserStoryDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
unblock: ->
|
unblock: ->
|
||||||
@rootscope.$broadcast("unblock", @scope.us)
|
@rootscope.$broadcast("unblock", @scope.us)
|
||||||
|
|
||||||
|
|
||||||
delete: ->
|
delete: ->
|
||||||
#TODO: i18n
|
#TODO: i18n
|
||||||
title = "Delete User Story"
|
title = "Delete User Story"
|
||||||
|
|
|
@ -47,11 +47,12 @@ class WikiDetailController extends mixOf(taiga.Controller, taiga.PageMixin)
|
||||||
"$log",
|
"$log",
|
||||||
"$appTitle",
|
"$appTitle",
|
||||||
"$tgNavUrls",
|
"$tgNavUrls",
|
||||||
|
"$tgAnalytics",
|
||||||
"tgLoader"
|
"tgLoader"
|
||||||
]
|
]
|
||||||
|
|
||||||
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location, @filter, @log, @appTitle,
|
constructor: (@scope, @rootscope, @repo, @confirm, @rs, @params, @q, @location,
|
||||||
@navUrls, tgLoader) ->
|
@filter, @log, @appTitle, @navUrls, @analytics, tgLoader) ->
|
||||||
@scope.projectSlug = @params.pslug
|
@scope.projectSlug = @params.pslug
|
||||||
@scope.wikiSlug = @params.slug
|
@scope.wikiSlug = @params.slug
|
||||||
@scope.sectionName = "Wiki"
|
@scope.sectionName = "Wiki"
|
||||||
|
@ -174,6 +175,7 @@ class WikiEditController extends WikiDetailController
|
||||||
if @scope.wiki.id
|
if @scope.wiki.id
|
||||||
@repo.save(@scope.wiki).then onSuccess, onError
|
@repo.save(@scope.wiki).then onSuccess, onError
|
||||||
else
|
else
|
||||||
|
@analytics.trackEvent("wikipage", "create", "create wiki page", 1)
|
||||||
@scope.wiki.project = @scope.projectId
|
@scope.wiki.project = @scope.projectId
|
||||||
@scope.wiki.slug = @scope.wikiSlug
|
@scope.wiki.slug = @scope.wikiSlug
|
||||||
@repo.create("wiki", @scope.wiki).then onSuccess, onError
|
@repo.create("wiki", @scope.wiki).then onSuccess, onError
|
||||||
|
|
|
@ -34,7 +34,7 @@ module = angular.module("taigaWiki")
|
||||||
## Wiki Main Directive
|
## Wiki Main Directive
|
||||||
#############################################################################
|
#############################################################################
|
||||||
|
|
||||||
WikiNavDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
|
WikiNavDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $analytics, $loading) ->
|
||||||
template = _.template("""
|
template = _.template("""
|
||||||
<header>
|
<header>
|
||||||
<h1>Links</h1>
|
<h1>Links</h1>
|
||||||
|
@ -132,6 +132,7 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
|
||||||
|
|
||||||
promise = $tgrepo.create("wiki-links", {project: $scope.projectId, title: newLink, href: slugify(newLink)})
|
promise = $tgrepo.create("wiki-links", {project: $scope.projectId, title: newLink, href: slugify(newLink)})
|
||||||
promise.then ->
|
promise.then ->
|
||||||
|
$analytics.trackEvent("wikilink", "create", "create wiki link", 1)
|
||||||
loadPromise = $ctrl.loadWikiLinks()
|
loadPromise = $ctrl.loadWikiLinks()
|
||||||
loadPromise.then ->
|
loadPromise.then ->
|
||||||
$loading.finish($el.find(".new"))
|
$loading.finish($el.find(".new"))
|
||||||
|
@ -166,4 +167,5 @@ WikiNavDirective = ($tgrepo, $log, $location, $confirm, $navUrls, $loading) ->
|
||||||
|
|
||||||
return {link:link}
|
return {link:link}
|
||||||
|
|
||||||
module.directive("tgWikiNav", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", "$tgNavUrls", "$tgLoading", WikiNavDirective])
|
module.directive("tgWikiNav", ["$tgRepo", "$log", "$tgLocation", "$tgConfirm", "$tgNavUrls",
|
||||||
|
"$tgAnalytics", "$tgLoading", WikiNavDirective])
|
||||||
|
|
Loading…
Reference in New Issue