Improve 'create project' (new, import, duplicate)
parent
466ba6de1e
commit
0cfef30885
|
@ -11,6 +11,7 @@
|
|||
- Add thumbnails and preview for PSD files.
|
||||
- Add thumbnails and preview for SVG files.
|
||||
- Improve add-members form: Now users can select between their contacts or type an email.
|
||||
- New project creation with importing
|
||||
- i18n:
|
||||
- Add japanese (ja) translation.
|
||||
- Add korean (ko) translation.
|
||||
|
|
|
@ -15,6 +15,7 @@ window.taigaConfig = {
|
|||
"privacyPolicyUrl": null,
|
||||
"termsOfServiceUrl": null,
|
||||
"maxUploadFileSize": null,
|
||||
"importers": [],
|
||||
"contribPlugins": []
|
||||
}
|
||||
|
||||
|
|
|
@ -126,6 +126,54 @@ configure = ($routeProvider, $locationProvider, $httpProvider, $provide, $tgEven
|
|||
controllerAs: "vm"
|
||||
}
|
||||
)
|
||||
|
||||
# Project
|
||||
$routeProvider.when("/project/new",
|
||||
{
|
||||
title: "PROJECT.CREATE.TITLE",
|
||||
templateUrl: "projects/create/create-project.html",
|
||||
loader: true,
|
||||
controller: "CreateProjectCtrl",
|
||||
controllerAs: "vm"
|
||||
}
|
||||
)
|
||||
|
||||
# Project - scrum
|
||||
$routeProvider.when("/project/new/scrum",
|
||||
{
|
||||
title: "PROJECT.CREATE.TITLE",
|
||||
template: "<tg-create-project-form type=\"scrum\"></tg-create-project-form>",
|
||||
loader: true
|
||||
}
|
||||
)
|
||||
|
||||
# Project - kanban
|
||||
$routeProvider.when("/project/new/kanban",
|
||||
{
|
||||
title: "PROJECT.CREATE.TITLE",
|
||||
template: "<tg-create-project-form type=\"kanban\"></tg-create-project-form>",
|
||||
loader: true
|
||||
}
|
||||
)
|
||||
|
||||
# Project - duplicate
|
||||
$routeProvider.when("/project/new/duplicate",
|
||||
{
|
||||
title: "PROJECT.CREATE.TITLE",
|
||||
template: "<tg-duplicate-project></tg-duplicate-project>",
|
||||
loader: true
|
||||
}
|
||||
)
|
||||
|
||||
# Project - import
|
||||
$routeProvider.when("/project/new/import/:platform?",
|
||||
{
|
||||
title: "PROJECT.CREATE.TITLE",
|
||||
template: "<tg-import-project></tg-import-project>",
|
||||
loader: true
|
||||
}
|
||||
)
|
||||
|
||||
# Project
|
||||
$routeProvider.when("/project/:pslug/",
|
||||
{
|
||||
|
|
|
@ -105,6 +105,7 @@ class AuthService extends taiga.Service
|
|||
return @rootscope.user
|
||||
|
||||
userData = @storage.get("userInfo")
|
||||
|
||||
if userData
|
||||
user = @model.make_model("users", userData)
|
||||
@rootscope.user = user
|
||||
|
|
|
@ -62,7 +62,12 @@ urls = {
|
|||
"cancel-account": "/cancel-account/:token"
|
||||
"register": "/register"
|
||||
"invitation": "/invitation/:token"
|
||||
"create-project": "/create-project"
|
||||
"create-project": "/project/new"
|
||||
"create-project-scrum": "/project/new/scrum"
|
||||
"create-project-kanban": "/project/new/kanban"
|
||||
"create-project-duplicate": "/project/new/duplicate"
|
||||
"create-project-import": "/project/new/import"
|
||||
"create-project-import-platform": "/project/new/import/:platform"
|
||||
|
||||
"profile": "/profile"
|
||||
"user-profile": "/profile/:username"
|
||||
|
|
|
@ -37,19 +37,6 @@ class RepositoryService extends taiga.Service
|
|||
resolveUrlForAttributeModel: (model) ->
|
||||
return @urls.resolve(model.getName(), model.parent)
|
||||
|
||||
create: (name, data, dataTypes={}, extraParams={}) ->
|
||||
defered = @q.defer()
|
||||
url = @urls.resolve(name)
|
||||
|
||||
promise = @http.post(url, JSON.stringify(data), extraParams)
|
||||
promise.success (_data, _status) =>
|
||||
defered.resolve(@model.make_model(name, _data, null, dataTypes))
|
||||
|
||||
promise.error (data, status) =>
|
||||
defered.reject(data)
|
||||
|
||||
return defered.promise
|
||||
|
||||
remove: (model, params={}) ->
|
||||
defered = @q.defer()
|
||||
url = @.resolveUrlForModel(model)
|
||||
|
|
|
@ -216,6 +216,9 @@ module.directive("tgToggleComment", ToggleCommentDirective)
|
|||
|
||||
ProjectUrl = ($navurls) ->
|
||||
get = (project) ->
|
||||
if project.toJS
|
||||
project = project.toJS()
|
||||
|
||||
ctx = {project: project.slug}
|
||||
|
||||
if project.is_backlog_activated and project.my_permissions.indexOf("view_us") > -1
|
||||
|
@ -353,12 +356,18 @@ module.directive("tgCapslock", [Capslock])
|
|||
|
||||
LightboxClose = () ->
|
||||
template = """
|
||||
<a class="close" href="" title="{{'COMMON.CLOSE' | translate}}">
|
||||
<a class="close" ng-click="onClose()" href="" title="{{'COMMON.CLOSE' | translate}}">
|
||||
<tg-svg svg-icon="icon-close"></tg-svg>
|
||||
</a>
|
||||
"""
|
||||
|
||||
link = (scope, elm, attrs) ->
|
||||
|
||||
return {
|
||||
scope: {
|
||||
onClose: '&'
|
||||
},
|
||||
link: link,
|
||||
template: template
|
||||
}
|
||||
|
||||
|
|
|
@ -207,13 +207,16 @@ class ConfirmService extends taiga.Service
|
|||
|
||||
return defered.promise
|
||||
|
||||
loader: (title, message) ->
|
||||
loader: (title, message, spin=false) ->
|
||||
el = angular.element(".lightbox-generic-loading")
|
||||
|
||||
# Render content
|
||||
el.find(".title").html(title) if title
|
||||
el.find(".message").html(message) if message
|
||||
|
||||
if spin
|
||||
el.find(".spin").removeClass("hidden")
|
||||
|
||||
return {
|
||||
start: => @lightboxService.open(el)
|
||||
stop: => @lightboxService.close(el)
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
# Copyright (C) 2014-2016 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014-2016 David Barragán Merino <bameda@dbarragan.com>
|
||||
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
||||
# Copyright (C) 2014-2016 Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
|
||||
# Copyright (C) 2014-2016 Xavi Julian <xavier.julian@kaleidos.net>
|
||||
#
|
||||
# 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/importer.coffee
|
||||
###
|
||||
|
||||
module = angular.module("taigaCommon")
|
||||
|
||||
|
||||
ImportProjectButtonDirective = ($rs, $confirm, $location, $navUrls, $translate, $lightboxFactory, currentUserService, $tgAuth) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
getRestrictionError = (result) ->
|
||||
if result.headers
|
||||
errorKey = ''
|
||||
|
||||
user = currentUserService.getUser()
|
||||
maxMemberships = 0
|
||||
|
||||
if result.headers.isPrivate
|
||||
privateError = !currentUserService.canCreatePrivateProjects().valid
|
||||
maxMemberships = null
|
||||
|
||||
if user.get('max_memberships_private_projects') != null && result.headers.memberships >= user.get('max_memberships_private_projects')
|
||||
membersError = true
|
||||
else
|
||||
membersError = false
|
||||
|
||||
if privateError && membersError
|
||||
errorKey = 'private-space-members'
|
||||
maxMemberships = user.get('max_memberships_private_projects')
|
||||
else if privateError
|
||||
errorKey = 'private-space'
|
||||
else if membersError
|
||||
errorKey = 'private-members'
|
||||
maxMemberships = user.get('max_memberships_private_projects')
|
||||
|
||||
else
|
||||
publicError = !currentUserService.canCreatePublicProjects().valid
|
||||
|
||||
if user.get('max_memberships_public_projects') != null && result.headers.memberships >= user.get('max_memberships_public_projects')
|
||||
membersError = true
|
||||
else
|
||||
membersError = false
|
||||
|
||||
if publicError && membersError
|
||||
errorKey = 'public-space-members'
|
||||
maxMemberships = user.get('max_memberships_public_projects')
|
||||
else if publicError
|
||||
errorKey = 'public-space'
|
||||
else if membersError
|
||||
errorKey = 'public-members'
|
||||
maxMemberships = user.get('max_memberships_public_projects')
|
||||
|
||||
return {
|
||||
key: errorKey,
|
||||
values: {
|
||||
max_memberships: maxMemberships,
|
||||
members: result.headers.memberships
|
||||
}
|
||||
}
|
||||
else
|
||||
return false
|
||||
|
||||
$el.on "click", ".import-project-button", (event) ->
|
||||
event.preventDefault()
|
||||
$el.find("input.import-file").val("")
|
||||
$el.find("input.import-file").trigger("click")
|
||||
|
||||
$el.on "change", "input.import-file", (event) ->
|
||||
event.preventDefault()
|
||||
file = event.target.files[0]
|
||||
return if not file
|
||||
|
||||
loader = $confirm.loader($translate.instant("PROJECT.IMPORT.UPLOADING_FILE"))
|
||||
|
||||
onSuccess = (result) ->
|
||||
currentUserService.loadProjects().then () ->
|
||||
loader.stop()
|
||||
|
||||
if result.status == 202 # Async mode
|
||||
title = $translate.instant("PROJECT.IMPORT.ASYNC_IN_PROGRESS_TITLE")
|
||||
message = $translate.instant("PROJECT.IMPORT.ASYNC_IN_PROGRESS_MESSAGE")
|
||||
$confirm.success(title, message)
|
||||
|
||||
else # result.status == 201 # Sync mode
|
||||
ctx = {project: result.data.slug}
|
||||
$location.path($navUrls.resolve("project-admin-project-profile-details", ctx))
|
||||
msg = $translate.instant("PROJECT.IMPORT.SYNC_SUCCESS")
|
||||
$confirm.notify("success", msg)
|
||||
|
||||
onError = (result) ->
|
||||
$tgAuth.refresh().then () ->
|
||||
restrictionError = getRestrictionError(result)
|
||||
|
||||
loader.stop()
|
||||
|
||||
if restrictionError
|
||||
$lightboxFactory.create('tg-lb-import-error', {
|
||||
class: 'lightbox lightbox-import-error'
|
||||
}, restrictionError)
|
||||
|
||||
else
|
||||
errorMsg = $translate.instant("PROJECT.IMPORT.ERROR")
|
||||
|
||||
if result.status == 429 # TOO MANY REQUESTS
|
||||
errorMsg = $translate.instant("PROJECT.IMPORT.ERROR_TOO_MANY_REQUEST")
|
||||
else if result.data?._error_message
|
||||
errorMsg = $translate.instant("PROJECT.IMPORT.ERROR_MESSAGE", {error_message: result.data._error_message})
|
||||
$confirm.notify("error", errorMsg)
|
||||
|
||||
loader.start()
|
||||
$rs.projects.import(file, loader.update).then(onSuccess, onError)
|
||||
|
||||
return {link: link}
|
||||
|
||||
module.directive("tgImportProjectButton",
|
||||
["$tgResources", "$tgConfirm", "$location", "$tgNavUrls", "$translate", "tgLightboxFactory", "tgCurrentUserService", "$tgAuth",
|
||||
ImportProjectButtonDirective])
|
||||
|
||||
LbImportErrorDirective = (lightboxService) ->
|
||||
link = (scope, el, attrs) ->
|
||||
lightboxService.open(el)
|
||||
|
||||
scope.close = () ->
|
||||
lightboxService.close(el)
|
||||
return
|
||||
|
||||
return {
|
||||
templateUrl: "common/lightbox/lightbox-import-error.html",
|
||||
link: link
|
||||
}
|
||||
|
||||
LbImportErrorDirective.$inject = ["lightboxService"]
|
||||
|
||||
module.directive("tgLbImportError", LbImportErrorDirective)
|
|
@ -38,7 +38,7 @@ trim = @.taiga.trim
|
|||
class LightboxService extends taiga.Service
|
||||
constructor: (@animationFrame, @q, @rootScope) ->
|
||||
|
||||
open: ($el, onClose) ->
|
||||
open: ($el, onClose, onEsc) ->
|
||||
@.onClose = onClose
|
||||
|
||||
if _.isString($el)
|
||||
|
@ -68,7 +68,12 @@ class LightboxService extends taiga.Service
|
|||
docEl = angular.element(document)
|
||||
docEl.on "keydown.lightbox", (e) =>
|
||||
code = if e.keyCode then e.keyCode else e.which
|
||||
@.close($el) if code == 27
|
||||
if code == 27
|
||||
if onEsc
|
||||
@rootScope.$applyAsync(onEsc)
|
||||
else
|
||||
@.close($el)
|
||||
|
||||
|
||||
return defered.promise
|
||||
|
||||
|
@ -171,9 +176,11 @@ module.service("lightboxKeyboardNavigationService", LightboxKeyboardNavigationSe
|
|||
|
||||
LightboxDirective = (lightboxService) ->
|
||||
link = ($scope, $el, $attrs) ->
|
||||
$el.on "click", ".close", (event) ->
|
||||
event.preventDefault()
|
||||
lightboxService.close($el)
|
||||
|
||||
if !$attrs.$attr.visible
|
||||
$el.on "click", ".close", (event) ->
|
||||
event.preventDefault()
|
||||
lightboxService.close($el)
|
||||
|
||||
return {restrict: "C", link: link}
|
||||
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
module = angular.module("taigaProject")
|
||||
|
||||
createProjectRestrictionDirective = () ->
|
||||
return {
|
||||
templateUrl: "project/wizard-restrictions.html"
|
||||
}
|
||||
|
||||
module.directive('tgCreateProjectRestriction', [createProjectRestrictionDirective])
|
|
@ -29,94 +29,6 @@ debounce = @.taiga.debounce
|
|||
|
||||
module = angular.module("taigaProject")
|
||||
|
||||
CreateProject = ($rootscope, $repo, $confirm, $location, $navurls, $rs, $projectUrl, $loading, lightboxService, $cacheFactory, $translate, currentUserService, $auth) ->
|
||||
link = ($scope, $el, attrs) ->
|
||||
$scope.data = {}
|
||||
$scope.templates = []
|
||||
currentLoading = null
|
||||
|
||||
form = $el.find("form").checksley({"onlyOneErrorElement": true})
|
||||
|
||||
onSuccessSubmit = (response) ->
|
||||
# remove all $http cache
|
||||
# This is necessary when a project is created with the same name
|
||||
# than another deleted in the same session
|
||||
$cacheFactory.get('$http').removeAll()
|
||||
|
||||
currentLoading.finish()
|
||||
$rootscope.$broadcast("projects:reload")
|
||||
|
||||
$confirm.notify("success", $translate.instant("COMMON.SAVE"))
|
||||
|
||||
$location.url($projectUrl.get(response))
|
||||
lightboxService.close($el)
|
||||
currentUserService.loadProjects()
|
||||
|
||||
onErrorSubmit = (response) ->
|
||||
currentLoading.finish()
|
||||
form.setErrors(response)
|
||||
selectors = []
|
||||
for error_field in _.keys(response)
|
||||
selectors.push("[name=#{error_field}]")
|
||||
|
||||
submit = (event) =>
|
||||
event.preventDefault()
|
||||
|
||||
if not form.validate()
|
||||
return
|
||||
|
||||
currentLoading = $loading()
|
||||
.target(submitButton)
|
||||
.start()
|
||||
|
||||
promise = $repo.create("projects", $scope.data)
|
||||
promise.then(onSuccessSubmit, onErrorSubmit)
|
||||
|
||||
openLightbox = ->
|
||||
$scope.data = {
|
||||
is_private: false
|
||||
}
|
||||
|
||||
if !$scope.templates.length
|
||||
$rs.projects.templates().then (result) =>
|
||||
$scope.templates = result
|
||||
$scope.data.creation_template = _.head(_.filter($scope.templates, (x) -> x.slug == "scrum")).id
|
||||
else
|
||||
$scope.data.creation_template = _.head(_.filter($scope.templates, (x) -> x.slug == "scrum")).id
|
||||
|
||||
$scope.canCreatePrivateProjects = currentUserService.canCreatePrivateProjects()
|
||||
$scope.canCreatePublicProjects = currentUserService.canCreatePublicProjects()
|
||||
|
||||
lightboxService.open($el)
|
||||
|
||||
submitButton = $el.find(".submit-button")
|
||||
|
||||
$el.on "submit", "form", submit
|
||||
|
||||
$el.on "click", ".close", (event) ->
|
||||
event.preventDefault()
|
||||
lightboxService.close($el)
|
||||
|
||||
$scope.$on "$destroy", ->
|
||||
$el.off()
|
||||
|
||||
$auth.refresh().then () ->
|
||||
openLightbox()
|
||||
|
||||
directive = {
|
||||
link: link,
|
||||
templateUrl: "project/wizard-create-project.html"
|
||||
scope: {}
|
||||
}
|
||||
|
||||
return directive
|
||||
|
||||
|
||||
module.directive("tgLbCreateProject", ["$rootScope", "$tgRepo", "$tgConfirm",
|
||||
"$location", "$tgNavUrls", "$tgResources", "$projectUrl", "$tgLoading",
|
||||
"lightboxService", "$cacheFactory", "$translate", "tgCurrentUserService", "$tgAuth", CreateProject])
|
||||
|
||||
|
||||
#############################################################################
|
||||
## Delete Project Lightbox Directive
|
||||
#############################################################################
|
||||
|
|
|
@ -203,6 +203,31 @@ urls = {
|
|||
|
||||
# Stats
|
||||
"stats-discover": "/stats/discover"
|
||||
|
||||
# Importers
|
||||
"importers-trello-auth-url": "/importers/trello/auth_url"
|
||||
"importers-trello-authorize": "/importers/trello/authorize"
|
||||
"importers-trello-list-projects": "/importers/trello/list_projects"
|
||||
"importers-trello-list-users": "/importers/trello/list_users"
|
||||
"importers-trello-import-project": "/importers/trello/import_project"
|
||||
|
||||
"importers-jira-auth-url": "/importers/jira/auth_url"
|
||||
"importers-jira-authorize": "/importers/jira/authorize"
|
||||
"importers-jira-list-projects": "/importers/jira/list_projects"
|
||||
"importers-jira-list-users": "/importers/jira/list_users"
|
||||
"importers-jira-import-project": "/importers/jira/import_project"
|
||||
|
||||
"importers-github-auth-url": "/importers/github/auth_url"
|
||||
"importers-github-authorize": "/importers/github/authorize"
|
||||
"importers-github-list-projects": "/importers/github/list_projects"
|
||||
"importers-github-list-users": "/importers/github/list_users"
|
||||
"importers-github-import-project": "/importers/github/import_project"
|
||||
|
||||
"importers-asana-auth-url": "/importers/asana/auth_url"
|
||||
"importers-asana-authorize": "/importers/asana/authorize"
|
||||
"importers-asana-list-projects": "/importers/asana/list_projects"
|
||||
"importers-asana-list-users": "/importers/asana/list_users"
|
||||
"importers-asana-import-project": "/importers/asana/import_project"
|
||||
}
|
||||
|
||||
# Initialize api urls service
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.6 KiB |
|
@ -203,7 +203,6 @@
|
|||
"CONFIRM_DELETE": "Remeber that all values in this custom field will be deleted.\n Are you sure you want to continue?"
|
||||
},
|
||||
"FILTERS": {
|
||||
"TITLE": "filters",
|
||||
"INPUT_PLACEHOLDER": "Subject or reference",
|
||||
"TITLE_ACTION_FILTER_BUTTON": "search",
|
||||
"TITLE": "Filters",
|
||||
|
@ -875,10 +874,8 @@
|
|||
"SECTION_TITLE": "Your projects",
|
||||
"PLACEHOLDER_SEARCH": "Search in...",
|
||||
"ACTION_CREATE_PROJECT": "Create project",
|
||||
"ACTION_IMPORT_PROJECT": "Import project",
|
||||
"MANAGE_PROJECTS": "Manage projects",
|
||||
"TITLE_CREATE_PROJECT": "Create project",
|
||||
"TITLE_IMPORT_PROJECT": "Import project",
|
||||
"TITLE_PRVIOUS_PROJECT": "Show previous projects",
|
||||
"TITLE_NEXT_PROJECT": "Show next projects",
|
||||
"HELP_TITLE": "Taiga Support Page",
|
||||
|
@ -904,44 +901,6 @@
|
|||
"DISCOVER": "Discover",
|
||||
"ACTION_REORDER": "Drag & drop to reorder"
|
||||
},
|
||||
"IMPORT": {
|
||||
"TITLE": "Importing Project",
|
||||
"UPLOADING_FILE": "Uploading dump file",
|
||||
"DESCRIPTION": "This process can take a while, please keep the window open.",
|
||||
"ASYNC_IN_PROGRESS_TITLE": "Our Oompa Loompas are importing your project",
|
||||
"ASYNC_IN_PROGRESS_MESSAGE": "This process could take a few minutes <br/> We will send you an email when ready",
|
||||
"UPLOAD_IN_PROGRESS_MESSAGE": "Uploaded {{uploadedSize}} of {{totalSize}}",
|
||||
"ERROR": "Our Oompa Loompas have some problems importing your dump data. Please try again.",
|
||||
"ERROR_TOO_MANY_REQUEST": "Sorry, our Oompa Loompas are very busy right now. Please try again in a few minutes.",
|
||||
"ERROR_MESSAGE": "Our Oompa Loompas have some problems importing your dump data: {{error_message}}",
|
||||
"ERROR_MAX_SIZE_EXCEEDED": "'{{fileName}}' ({{fileSize}}) is too heavy for our Oompa Loompas, try it with a smaller than ({{maxFileSize}})",
|
||||
"SYNC_SUCCESS": "Your project has been imported successfuly",
|
||||
"PROJECT_RESTRICTIONS": {
|
||||
"PROJECT_MEMBERS_DESC": "The project you are trying to import has {{members}} members, unfortunately, your current plan allows for a maximum of {{max_memberships}} members per project. If you would like to increase that limit please contact the administrator.",
|
||||
"PRIVATE_PROJECTS_SPACE": {
|
||||
"TITLE": "Unfortunately, your current plan does not allow for additional private projects",
|
||||
"DESC": "The project you are trying to import is private. Unfortunately, your current plan does not allow for additional private projects."
|
||||
},
|
||||
"PUBLIC_PROJECTS_SPACE": {
|
||||
"TITLE": "Unfortunately, your current plan does not allow for additional public projects",
|
||||
"DESC": "The project you are trying to import is public. Unfortunately, your current plan does not allow additional public projects."
|
||||
},
|
||||
"PRIVATE_PROJECTS_MEMBERS": {
|
||||
"TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per private project"
|
||||
},
|
||||
"PUBLIC_PROJECTS_MEMBERS": {
|
||||
"TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per public project."
|
||||
},
|
||||
"PRIVATE_PROJECTS_SPACE_MEMBERS": {
|
||||
"TITLE": "Unfortunately your current plan doesn't allow additional private projects or an increase of more than {{max_memberships}} members per private project",
|
||||
"DESC": "The project that you are trying to import is private and has {{members}} members."
|
||||
},
|
||||
"PUBLIC_PROJECTS_SPACE_MEMBERS": {
|
||||
"TITLE": "Unfortunately your current plan doesn't allow additional public projects or an increase of more than {{max_memberships}} members per public project",
|
||||
"DESC": "The project that you are trying to import is public and has more than {{members}} members."
|
||||
}
|
||||
}
|
||||
},
|
||||
"LIKE_BUTTON": {
|
||||
"LIKE": "Like",
|
||||
"LIKED": "Liked",
|
||||
|
@ -966,6 +925,152 @@
|
|||
"CONTACT_BUTTON": {
|
||||
"CONTACT_TITLE": "Contact the project team",
|
||||
"CONTACT_BUTTON": "Contact the project"
|
||||
},
|
||||
"CREATE": {
|
||||
"TITLE": "Create Project",
|
||||
"FRESH": "Fresh and clean. So exciting!",
|
||||
"CHOOSE_TEMPLATE": "Which template fits your project better?",
|
||||
"TEMPLATE_SCRUM": "Scrum",
|
||||
"TEMPLATE_SCRUM_DESC": "Prioritize and solve your tasks in short time cycles.",
|
||||
"TEMPLATE_SCRUM_LONGDESC": "Scrum is an iterative and incremental agile software development methodology for managing product development.\nThe product backlog is what will ultimately be delivered, ordered into the sequence in which it should be delivered. Product Backlogs are broken into manageable, executable chunks named sprints. Every certain amount of time the team initiates a new sprint and commits to deliver a certain number of user stories from the backlog, in accordance with their skills, abilities and resources. The project advances as the backlog becomes depleted.",
|
||||
"TEMPLATE_KANBAN": "Kanban",
|
||||
"TEMPLATE_KANBAN_DESC": "Keep a constant workflow on independent tasks",
|
||||
"TEMPLATE_KANBAN_LONGDESC": "The Kanban methodology is used to divide project development (any sort of project) into stages.\nA kanban card is like an index card or post-it note that details every task (or user story) in a project that needs to be completed. The Kanban board is used to move each card from one state of completion to the next and in so doing, helps track progress.",
|
||||
"DUPLICATE": "Duplicate project",
|
||||
"DUPLICATE_DESC": "Start clean and keep your configuration",
|
||||
"IMPORT": "Import project",
|
||||
"IMPORT_DESC": "Import from Taiga, Trello, Jira, Github...",
|
||||
"INVITE": "Invite to the project",
|
||||
"SOLO_PROJECT": "You'll be alone in this project",
|
||||
"INVITE_LATER": "(You'll be able to invite more members later)",
|
||||
"BACK": "Back",
|
||||
"MAX_PRIVATE_PROJECTS": "Unfortunately, You've reached the maximum number of private projects.\nIf you would like to increase the current limit please contact the administrator.",
|
||||
"MAX_PUBLIC_PROJECTS": "Unfortunately, You've reached the maximum number of public projects.\nIf you would like to increase the current limit please contact the administrator.",
|
||||
"PUBLIC_PROJECT": "Public Project",
|
||||
"PRIVATE_PROJECT": "Private Project",
|
||||
"MAX_MEMBERS": "Unfortunately, your current plan allows for a maximum of {{max_memberships}} members per project.\n Unselect some members or contact the administrator.",
|
||||
"PRIVATE_PROJECT": "Private Project"
|
||||
},
|
||||
"COMMON": {
|
||||
"DETAILS": "New project details",
|
||||
"PROJECT_TITLE": "Project Name",
|
||||
"PROJECT_DESCRIPTION": "Project Description"
|
||||
},
|
||||
"DUPLICATE": {
|
||||
"TITLE": "Duplicate Project",
|
||||
"DESCRIPTION": "Start clean and keep your configuration",
|
||||
"SELECT_PLACEHOLDER": "Choose an existing project to duplicate",
|
||||
"DETAILS": "New project details",
|
||||
"CREATE_PROJECT_TEXT": "Fresh and clean. So exciting!",
|
||||
"CHOOSE_TEMPLATE_TITLE": "More info about project templates",
|
||||
"CHOOSE_TEMPLATE_INFO": "More info",
|
||||
"PROJECT_DETAILS": "Project Details",
|
||||
"PUBLIC_PROJECT": "Public Project",
|
||||
"PRIVATE_PROJECT": "Private Project",
|
||||
"CREATE_PROJECT": "Create project",
|
||||
"CHANGE_PLANS": "change plans"
|
||||
},
|
||||
"IMPORT": {
|
||||
"TITLE": "Import Project",
|
||||
"IMPORT": "Import",
|
||||
"ARCHIVED": "Archived",
|
||||
"ARCHIVED_DESCRIPTION": "You have archived projects, Do you want to import your archived projects?",
|
||||
"WHO_IS": "Their tasks will be assigned to ...",
|
||||
"WRITE_EMAIL": "Or if you want, write the email that their use in Taiga",
|
||||
"SEARCH_CONTACT": "Or if you want, search in your contacts",
|
||||
"WRITE_EMAIL_LABEL": "Write the email that their use in Taiga",
|
||||
"EMAIL_NOT_FOUND": "We did not find any users with that email",
|
||||
"ACCEEDE": "Acceede",
|
||||
"PROJECT_MEMBERS": "Project Members",
|
||||
"PROCESS_DESCRIPTION": "Tell us who from Taiga you want to assign the tasks of {{platform}}",
|
||||
"MATCH": "Is <strong>{{user_external}}</strong> the same person as <strong>{{user_internal}}</strong>?",
|
||||
"CHOOSE": "Select user",
|
||||
"LINKS": "Links with {{platform}}",
|
||||
"LINKS_DESCRIPTION": "Do you want to keep the link of each item with the original {{platform}} card?",
|
||||
"WARNING_MAIL_USER": "Note that if the user does not have a Taiga account we will not be able to assign the tasks to him.",
|
||||
"ASSIGN": "Assign",
|
||||
"PROJECT_RESTRICTIONS": {
|
||||
"PROJECT_MEMBERS_DESC_PRIVATE": "The project you are trying to import has {{members}} members including you, unfortunately, your current plan allows for a maximum of {{max_memberships}} members per private project. If you would like to increase that limit please contact the administrator.",
|
||||
"PROJECT_MEMBERS_DESC_PUBLIC": "The project you are trying to import has {{members}} members including you, unfortunately, your current plan allows for a maximum of {{max_memberships}} members per public project. If you would like to increase that limit please contact the administrator.",
|
||||
"ACCOUNT_ALLOW_MEMBERS": "Your account only allows {{members}} members",
|
||||
"PRIVATE_PROJECTS_SPACE": {
|
||||
"TITLE": "Unfortunately, your current plan does not allow for additional private projects",
|
||||
"DESC": "The project you are trying to import is private. Unfortunately, your current plan does not allow for additional private projects."
|
||||
},
|
||||
"PUBLIC_PROJECTS_SPACE": {
|
||||
"TITLE": "Unfortunately, your current plan does not allow for additional public projects",
|
||||
"DESC": "The project you are trying to import is public. Unfortunately, your current plan does not allow additional public projects."
|
||||
},
|
||||
"PRIVATE_PROJECTS_MEMBERS": {
|
||||
"TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per private project"
|
||||
},
|
||||
"PUBLIC_PROJECTS_MEMBERS": {
|
||||
"TITLE": "Your current plan allows for a maximum of {{max_memberships}} members per public project."
|
||||
},
|
||||
"PRIVATE_PROJECTS_SPACE_MEMBERS": {
|
||||
"TITLE": "Unfortunately your current plan doesn't allow additional private projects or an increase of more than {{max_memberships}} members per private project",
|
||||
"DESC": "The project that you are trying to import is private and has {{members}} members."
|
||||
},
|
||||
"PUBLIC_PROJECTS_SPACE_MEMBERS": {
|
||||
"TITLE": "Unfortunately your current plan doesn't allow additional public projects or an increase of more than {{max_memberships}} members per public project",
|
||||
"DESC": "The project that you are trying to import is public and has more than {{members}} members."
|
||||
}
|
||||
},
|
||||
"IN_PROGRESS": {
|
||||
"TITLE": "Importing Project",
|
||||
"DESCRIPTION": "This process can take a while, please keep the window open."
|
||||
},
|
||||
"WARNING": {
|
||||
"TITLE": "Some taks will be unassigned",
|
||||
"DESCRIPTION": "There are still unidentified people. The cards assigned to these people will remain unassigned. Check all the contacts to not lose that information.",
|
||||
"CHECK": "Check contacts"
|
||||
},
|
||||
"TAIGA": {
|
||||
"SELECTOR": "Import your Taiga project"
|
||||
},
|
||||
"TRELLO": {
|
||||
"TITLE": "Trello",
|
||||
"SELECTOR": "Import your Trello boards into Taiga",
|
||||
"CHOOSE_BOARD": "Choose board that you want to import"
|
||||
},
|
||||
"GITHUB": {
|
||||
"TITLE": "Github",
|
||||
"SELECTOR": "Import your Github project issues",
|
||||
"CHOOSE_BOARD": "Find the project you want to import",
|
||||
"PROJECT_MEMBERS": "Project Members",
|
||||
"HOW_DO_YOU_WANT_TO_IMPORT": "How do you want to import your issues into Taiga?",
|
||||
"KANBAN_PROJECT": "As user stories in a kanban project",
|
||||
"KANBAN_PROJECT_DESCRIPTION": "After that you can enable scrum with backlog.",
|
||||
"SCRUM_PROJECT": "As user stories in a scrum project",
|
||||
"SCRUM_PROJECT_DESCRIPTION": "After that you can enable kanban mode.",
|
||||
"ISSUES_PROJECT": "As issues",
|
||||
"ISSUES_PROJECT_DESCRIPTION": "You will not able to use your issues in kanban or scrum mode. You will be able to enable kanban or scrum for new user stories"
|
||||
},
|
||||
"ASANA": {
|
||||
"TITLE": "Asana",
|
||||
"SELECTOR": "Import your Asana project and choose how to manage it",
|
||||
"CHOOSE_BOARD": "Choose project that you want to import",
|
||||
"KANBAN_PROJECT": "Kanban",
|
||||
"SCRUM_PROJECT": "Scrum",
|
||||
"CREATE_AS_SCRUM_DESCRIPTION": "The tasks and sub-tasks of your project will be created as Taiga user stories and tasks.",
|
||||
"CREATE_AS_KANBAN_DESCRIPTION": "The tasks and sub-tasks of your project will be created as Taiga user stories and tasks.",
|
||||
"PROJECT_MEMBERS": "Project Members"
|
||||
},
|
||||
"JIRA": {
|
||||
"TITLE": "Jira",
|
||||
"CHOOSE_PROJECT": "Choose project or board that you want to import",
|
||||
"SELECTOR": "Import your Jira project and choose how to manage it",
|
||||
"URL": "Your Jira URL",
|
||||
"PROJECT_MEMBERS": "Project Members",
|
||||
"KANBAN_PROJECT": "Kanban",
|
||||
"SCRUM_PROJECT": "Scrum",
|
||||
"ISSUES_PROJECT": "Issues",
|
||||
"CREATE_AS_SCRUM_DESCRIPTION": "The issues and sub-issues of your project will be created as Taiga user stories and tasks.",
|
||||
"CREATE_AS_KANBAN_DESCRIPTION": "The issues and sub-issues of your project will be created as Taiga user stories and tasks.",
|
||||
"CREATE_AS_ISSUES_DESCRIPTION": "What do you want to do with sub-issues from the Jira project? (Taiga doesn't allow sub-issues)",
|
||||
"CREATE_NEW_ISSUES": "Convert sub-issues to new Taiga issues",
|
||||
"NOT_CREATE_NEW_ISSUES": "Do not import sub-issues"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LIGHTBOX": {
|
||||
|
@ -1518,20 +1623,7 @@
|
|||
"THEME_DEFAULT": "-- use default theme --"
|
||||
}
|
||||
},
|
||||
"WIZARD": {
|
||||
"SECTION_TITLE_CREATE_PROJECT": "Create Project",
|
||||
"CREATE_PROJECT_TEXT": "Fresh and clean. So exciting!",
|
||||
"CHOOSE_TEMPLATE": "Which template fits your project best?",
|
||||
"CHOOSE_TEMPLATE_TITLE": "More info about project templates",
|
||||
"CHOOSE_TEMPLATE_INFO": "More info",
|
||||
"PROJECT_DETAILS": "Project Details",
|
||||
"PUBLIC_PROJECT": "Public Project",
|
||||
"PRIVATE_PROJECT": "Private Project",
|
||||
"CREATE_PROJECT": "Create project",
|
||||
"MAX_PRIVATE_PROJECTS": "You've reached the maximum number of private projects",
|
||||
"MAX_PUBLIC_PROJECTS": "Unfortunately, you've reached the maximum number of public projects",
|
||||
"CHANGE_PLANS": "change plans"
|
||||
},
|
||||
|
||||
"WIKI": {
|
||||
"PAGE_TITLE": "{{wikiPageName}} - Wiki - {{projectName}}",
|
||||
"PAGE_DESCRIPTION": "Last edition on {{lastModifiedDate}} ({{totalEditions}} editions in total) Content: {{ wikiPageContent }}",
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Andrey Antukh <niwi@niwi.nz>
|
||||
# Copyright (C) 2014-2016 Jesús Espino Garcia <jespinog@gmail.com>
|
||||
# Copyright (C) 2014-2016 David Barragán Merino <bameda@dbarragan.com>
|
||||
# Copyright (C) 2014-2016 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
||||
# Copyright (C) 2014-2016 Juan Francisco Alcántara <juanfran.alcantara@kaleidos.net>
|
||||
# Copyright (C) 2014-2016 Xavi Julian <xavier.julian@kaleidos.net>
|
||||
#
|
||||
# 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/components/click-input-file.directive.coffee
|
||||
###
|
||||
|
||||
ClickInputFile = () ->
|
||||
return {
|
||||
link: (scope, el) ->
|
||||
el.on 'click', (e) ->
|
||||
if !$(e.target).is('input')
|
||||
e.preventDefault()
|
||||
inputFile = el.find('input[type="file"]')
|
||||
inputFile.val('')
|
||||
inputFile.trigger('click')
|
||||
|
||||
scope.$on "$destroy", -> el.off()
|
||||
}
|
||||
|
||||
angular.module("taigaComponents")
|
||||
.directive("tgClickInputFile", [ClickInputFile])
|
|
@ -27,7 +27,6 @@ FileChangeDirective = ($parse) ->
|
|||
scope.$on "$destroy", -> el.off()
|
||||
|
||||
return {
|
||||
require: "ngModel",
|
||||
restrict: "A",
|
||||
link: link
|
||||
}
|
||||
|
|
|
@ -40,12 +40,6 @@ describe "homeProjectListDirective", () ->
|
|||
|
||||
provide.value "tgCurrentUserService", mocks.currentUserService
|
||||
|
||||
_mockTgProjectsService = () ->
|
||||
mocks.projectsService = {
|
||||
newProject: sinon.stub()
|
||||
}
|
||||
provide.value "tgProjectsService", mocks.projectsService
|
||||
|
||||
_mockTranslateFilter = () ->
|
||||
mockTranslateFilter = (value) ->
|
||||
return value
|
||||
|
@ -55,7 +49,6 @@ describe "homeProjectListDirective", () ->
|
|||
module ($provide) ->
|
||||
provide = $provide
|
||||
_mockTgCurrentUserService()
|
||||
_mockTgProjectsService()
|
||||
_mockTranslateFilter()
|
||||
return null
|
||||
|
||||
|
@ -82,11 +75,3 @@ describe "homeProjectListDirective", () ->
|
|||
elm = createDirective()
|
||||
scope.$apply()
|
||||
expect(elm.isolateScope().vm.projects.size).to.be.equal(3)
|
||||
|
||||
it "home project list directive newProject", () ->
|
||||
elm = createDirective()
|
||||
scope.$apply()
|
||||
|
||||
expect(mocks.projectsService.newProject.callCount).to.be.equal(0)
|
||||
elm.isolateScope().vm.newProject()
|
||||
expect(mocks.projectsService.newProject.callCount).to.be.equal(1)
|
||||
|
|
|
@ -17,15 +17,12 @@
|
|||
# File: home-project-list.directive.coffee
|
||||
###
|
||||
|
||||
HomeProjectListDirective = (currentUserService, projectsService) ->
|
||||
HomeProjectListDirective = (currentUserService) ->
|
||||
link = (scope, el, attrs, ctrl) ->
|
||||
scope.vm = {}
|
||||
|
||||
taiga.defineImmutableProperty(scope.vm, "projects", () -> currentUserService.projects.get("recents"))
|
||||
|
||||
scope.vm.newProject = ->
|
||||
projectsService.newProject()
|
||||
|
||||
directive = {
|
||||
templateUrl: "home/projects/home-project-list.html"
|
||||
scope: {}
|
||||
|
@ -35,8 +32,7 @@ HomeProjectListDirective = (currentUserService, projectsService) ->
|
|||
return directive
|
||||
|
||||
HomeProjectListDirective.$inject = [
|
||||
"tgCurrentUserService",
|
||||
"tgProjectsService"
|
||||
"tgCurrentUserService"
|
||||
]
|
||||
|
||||
angular.module("taigaHome").directive("tgHomeProjectList", HomeProjectListDirective)
|
||||
|
|
|
@ -84,14 +84,7 @@ section.projects-empty(ng-if="vm.projects != undefined && vm.projects.size === 0
|
|||
p(translate="HOME.EMPTY_PROJECT_LIST")
|
||||
a.create-project-button.button-green(
|
||||
href="#"
|
||||
ng-click="vm.newProject()"
|
||||
tg-nav="create-project"
|
||||
title="{{'PROJECT.NAVIGATION.TITLE_CREATE_PROJECT' | translate}}"
|
||||
translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT"
|
||||
)
|
||||
span(tg-import-project-button)
|
||||
a.import-project-button.button-blackish(
|
||||
href="#"
|
||||
title="{{'PROJECT.NAVIGATION.TITLE_IMPORT_PROJECT' | translate}}"
|
||||
translate="PROJECT.NAVIGATION.ACTION_IMPORT_PROJECT"
|
||||
)
|
||||
input.import-file.hidden(type="file")
|
||||
|
|
|
@ -29,14 +29,6 @@ div.navbar-dropdown.dropdown-project-list
|
|||
div.create-options
|
||||
a.create-project-btn.button-green(
|
||||
href="#",
|
||||
ng-click="vm.newProject()",
|
||||
tg-nav="create-project"
|
||||
title="{{'PROJECT.NAVIGATION.ACTION_CREATE_PROJECT' | translate}}",
|
||||
translate="PROJECT.NAVIGATION.ACTION_CREATE_PROJECT")
|
||||
|
||||
span(tg-import-project-button)
|
||||
a.button-blackish.import-project-button(
|
||||
href=""
|
||||
title="{{'PROJECT.NAVIGATION.TITLE_IMPORT_PROJECT' | translate}}"
|
||||
)
|
||||
tg-svg(svg-icon="icon-upload")
|
||||
input.import-file.hidden(type="file")
|
||||
|
|
|
@ -0,0 +1,55 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: asana-import-project-form.controller.coffee
|
||||
###
|
||||
|
||||
class AsanaImportProjectFormController
|
||||
@.$inject = [
|
||||
"tgCurrentUserService"
|
||||
]
|
||||
|
||||
constructor: (@currentUserService) ->
|
||||
@.canCreatePublicProjects = @currentUserService.canCreatePublicProjects()
|
||||
@.canCreatePrivateProjects = @currentUserService.canCreatePrivateProjects()
|
||||
|
||||
@.projectForm = @.project.toJS()
|
||||
|
||||
@.platformName = "Asana"
|
||||
@.projectForm.is_private = false
|
||||
@.projectForm.keepExternalReference = false
|
||||
@.projectForm.project_type = "scrum"
|
||||
|
||||
if !@.canCreatePublicProjects.valid && @.canCreatePrivateProjects.valid
|
||||
@.projectForm.is_private = true
|
||||
|
||||
checkUsersLimit: () ->
|
||||
@.limitMembersPrivateProject = @currentUserService.canAddMembersPrivateProject(@.members.size)
|
||||
@.limitMembersPublicProject = @currentUserService.canAddMembersPublicProject(@.members.size)
|
||||
|
||||
saveForm: () ->
|
||||
@.onSaveProjectDetails({project: Immutable.fromJS(@.projectForm)})
|
||||
|
||||
canCreateProject: () ->
|
||||
if @.projectForm.is_private
|
||||
return @.canCreatePrivateProjects.valid
|
||||
else
|
||||
return @.canCreatePublicProjects.valid
|
||||
|
||||
isDisabled: () ->
|
||||
return !@.canCreateProject()
|
||||
|
||||
angular.module('taigaProjects').controller('AsanaImportProjectFormCtrl', AsanaImportProjectFormController)
|
|
@ -0,0 +1,40 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: asana-import-project-form.directive.coffee
|
||||
###
|
||||
|
||||
AsanaImportProjectFormDirective = () ->
|
||||
return {
|
||||
link: (scope, elm, attr, ctrl) ->
|
||||
scope.$watch('vm.members', ctrl.checkUsersLimit.bind(ctrl))
|
||||
|
||||
templateUrl:"projects/create/asana-import/asana-import-project-form/asana-import-project-form.html",
|
||||
controller: "AsanaImportProjectFormCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {
|
||||
members: '<',
|
||||
project: '<',
|
||||
onSaveProjectDetails: '&',
|
||||
onCancelForm: '&',
|
||||
fetchingUsers: '<'
|
||||
}
|
||||
}
|
||||
|
||||
AsanaImportProjectFormDirective.$inject = []
|
||||
|
||||
angular.module("taigaProjects").directive("tgAsanaImportProjectForm", AsanaImportProjectFormDirective)
|
|
@ -0,0 +1,63 @@
|
|||
.import-project-asana-form
|
||||
div(ng-include="'projects/create/import/import-header.html'")
|
||||
|
||||
.spin(tg-loading="vm.fetchingUsers")
|
||||
|
||||
form(
|
||||
ng-if="!vm.fetchingUsers",
|
||||
name="projectForm",
|
||||
ng-submit="vm.saveForm()"
|
||||
)
|
||||
div(ng-include="'projects/create/import-project-form-common/name.html'")
|
||||
div(ng-include="'projects/create/import-project-form-common/description.html'")
|
||||
.create-project-import-type(role="group")
|
||||
fieldset
|
||||
input(
|
||||
type="radio"
|
||||
name="project_type"
|
||||
id="template-scrum"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="'scrum'"
|
||||
ng-model="vm.projectForm.project_type"
|
||||
required
|
||||
)
|
||||
label(for="template-scrum")
|
||||
tg-svg(svg-icon="icon-scrum")
|
||||
span(translate="PROJECT.IMPORT.ASANA.SCRUM_PROJECT")
|
||||
fieldset
|
||||
input(
|
||||
type="radio"
|
||||
name="project_type"
|
||||
id="template-kanban"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="'kanban'"
|
||||
ng-model="vm.projectForm.project_type"
|
||||
required
|
||||
)
|
||||
label(for="template-kanban")
|
||||
tg-svg(svg-icon="icon-kanban")
|
||||
span(translate="PROJECT.IMPORT.ASANA.KANBAN_PROJECT")
|
||||
|
||||
p.create-project-import-type-info(
|
||||
ng-if="vm.projectForm.project_type == 'scrum'"
|
||||
translate='PROJECT.IMPORT.ASANA.CREATE_AS_SCRUM_DESCRIPTION'
|
||||
)
|
||||
p.create-project-import-type-info(
|
||||
ng-if="vm.projectForm.project_type == 'kanban'"
|
||||
translate='PROJECT.IMPORT.ASANA.CREATE_AS_KANBAN_DESCRIPTION'
|
||||
)
|
||||
div(ng-include="'projects/create/import-project-form-common/project-privacy.html'")
|
||||
tg-create-project-restrictions(
|
||||
is-private="vm.projectForm.is_private"
|
||||
can-create-public-projects="vm.canCreatePublicProjects"
|
||||
can-create-private-projects="vm.canCreatePrivateProjects"
|
||||
)
|
||||
tg-create-project-members-restrictions(
|
||||
is-private="vm.projectForm.is_private"
|
||||
limit-members-private-project="vm.limitMembersPrivateProject"
|
||||
limit-members-public-project="vm.limitMembersPublicProject"
|
||||
)
|
||||
div(ng-include="'projects/create/import-project-form-common/links.html'")
|
||||
div(ng-include="'projects/create/import-project-form-common/actions.html'")
|
|
@ -0,0 +1,57 @@
|
|||
.import-project-asana-form {
|
||||
@include create-project;
|
||||
}
|
||||
|
||||
.create-project-asana-import-type {
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
|
||||
&-question {
|
||||
align-content: stretch;
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
background: $white;
|
||||
border-right: 1px solid $whitish;
|
||||
transition: background .2s linear;
|
||||
|
||||
&:last-child {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
|
||||
&:checked {
|
||||
+label {
|
||||
background: rgba($primary, .1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
background: $white;
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
transition: background .2s ease-in;
|
||||
|
||||
&:hover {
|
||||
background: rgba($primary, .1);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&-name {
|
||||
@include font-type(normal);
|
||||
display: inline-block;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
&-description {
|
||||
@include font-type(light);
|
||||
@include font-size(small);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: asana-import.controller.coffee
|
||||
###
|
||||
|
||||
class AsanaImportController
|
||||
@.$inject = [
|
||||
'tgAsanaImportService',
|
||||
'$tgConfirm',
|
||||
'$translate',
|
||||
'tgImportProjectService',
|
||||
]
|
||||
|
||||
constructor: (@asanaImportService, @confirm, @translate, @importProjectService) ->
|
||||
@.step = 'autorization-asana'
|
||||
@.project = null
|
||||
taiga.defineImmutableProperty @, 'projects', () => return @asanaImportService.projects
|
||||
taiga.defineImmutableProperty @, 'members', () => return @asanaImportService.projectUsers
|
||||
|
||||
startProjectSelector: () ->
|
||||
@.step = 'project-select-asana'
|
||||
@asanaImportService.fetchProjects()
|
||||
|
||||
onSelectProject: (project) ->
|
||||
@.step = 'project-form-asana'
|
||||
@.project = project
|
||||
@.fetchingUsers = true
|
||||
|
||||
@asanaImportService.fetchUsers(@.project.get('id')).then () => @.fetchingUsers = false
|
||||
|
||||
onSaveProjectDetails: (project) ->
|
||||
@.project = project
|
||||
@.step = 'project-members-asana'
|
||||
|
||||
onCancelMemberSelection: () ->
|
||||
@.step = 'project-form-asana'
|
||||
|
||||
startImport: (users) ->
|
||||
loader = @confirm.loader(@translate.instant('PROJECT.IMPORT.IN_PROGRESS.TITLE'), @translate.instant('PROJECT.IMPORT.IN_PROGRESS.DESCRIPTION'), true)
|
||||
|
||||
loader.start()
|
||||
|
||||
promise = @asanaImportService.importProject(
|
||||
@.project.get('name'),
|
||||
@.project.get('description'),
|
||||
@.project.get('id'),
|
||||
users,
|
||||
@.project.get('keepExternalReference'),
|
||||
@.project.get('is_private')
|
||||
@.project.get('project_type')
|
||||
)
|
||||
|
||||
@importProjectService.importPromise(promise).then () => loader.stop()
|
||||
|
||||
submitUserSelection: (users) ->
|
||||
@.startImport(users)
|
||||
return null
|
||||
|
||||
angular.module('taigaProjects').controller('AsanaImportCtrl', AsanaImportController)
|
|
@ -0,0 +1,172 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: asana-import.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "AsanaImportCtrl", ->
|
||||
$provide = null
|
||||
$controller = null
|
||||
mocks = {}
|
||||
|
||||
_mockCurrentUserService = ->
|
||||
mocks.currentUserService = {
|
||||
canAddMembersPrivateProject: sinon.stub()
|
||||
canAddMembersPublicProject: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgCurrentUserService", mocks.currentUserService)
|
||||
|
||||
_mockAsanaImportService = ->
|
||||
mocks.asanaService = {
|
||||
fetchProjects: sinon.stub(),
|
||||
fetchUsers: sinon.stub(),
|
||||
importProject: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgAsanaImportService", mocks.asanaService)
|
||||
|
||||
_mockImportProjectService = ->
|
||||
mocks.importProjectService = {
|
||||
importPromise: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgImportProjectService", mocks.importProjectService)
|
||||
|
||||
_mockConfirm = ->
|
||||
mocks.confirm = {
|
||||
loader: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$tgConfirm", mocks.confirm)
|
||||
|
||||
_mockTranslate = ->
|
||||
mocks.translate = {
|
||||
instant: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$translate", mocks.translate)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockAsanaImportService()
|
||||
_mockConfirm()
|
||||
_mockTranslate()
|
||||
_mockImportProjectService()
|
||||
_mockCurrentUserService()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_$controller_) ->
|
||||
$controller = _$controller_
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProjects"
|
||||
|
||||
_setup()
|
||||
|
||||
it "start project selector", () ->
|
||||
ctrl = $controller("AsanaImportCtrl")
|
||||
ctrl.startProjectSelector()
|
||||
|
||||
expect(ctrl.step).to.be.equal('project-select-asana')
|
||||
expect(mocks.asanaService.fetchProjects).have.been.called
|
||||
|
||||
it "on select project reload projects", (done) ->
|
||||
project = Immutable.fromJS({
|
||||
id: 1,
|
||||
name: "project-name"
|
||||
})
|
||||
|
||||
mocks.asanaService.fetchUsers.promise().resolve()
|
||||
|
||||
ctrl = $controller("AsanaImportCtrl")
|
||||
|
||||
promise = ctrl.onSelectProject(project)
|
||||
|
||||
expect(ctrl.fetchingUsers).to.be.true
|
||||
|
||||
promise.then () ->
|
||||
expect(ctrl.fetchingUsers).to.be.false
|
||||
expect(ctrl.step).to.be.equal('project-form-asana')
|
||||
expect(ctrl.project).to.be.equal(project)
|
||||
done()
|
||||
|
||||
it "on save project details reload users", () ->
|
||||
project = Immutable.fromJS({
|
||||
id: 1,
|
||||
name: "project-name"
|
||||
})
|
||||
|
||||
ctrl = $controller("AsanaImportCtrl")
|
||||
ctrl.onSaveProjectDetails(project)
|
||||
|
||||
expect(ctrl.step).to.be.equal('project-members-asana')
|
||||
expect(ctrl.project).to.be.equal(project)
|
||||
|
||||
it "on select user init import", (done) ->
|
||||
users = Immutable.fromJS([
|
||||
{
|
||||
id: 0
|
||||
},
|
||||
{
|
||||
id: 1
|
||||
},
|
||||
{
|
||||
id: 2
|
||||
}
|
||||
])
|
||||
|
||||
loaderObj = {
|
||||
start: sinon.spy(),
|
||||
update: sinon.stub(),
|
||||
stop: sinon.spy()
|
||||
}
|
||||
|
||||
projectResult = {
|
||||
id: 3,
|
||||
name: "name"
|
||||
}
|
||||
|
||||
mocks.confirm.loader.returns(loaderObj)
|
||||
|
||||
mocks.importProjectService.importPromise.promise().resolve()
|
||||
|
||||
ctrl = $controller("AsanaImportCtrl")
|
||||
ctrl.project = Immutable.fromJS({
|
||||
id: 1,
|
||||
name: 'project-name',
|
||||
description: 'project-description',
|
||||
keepExternalReference: false,
|
||||
is_private: true
|
||||
})
|
||||
|
||||
|
||||
mocks.asanaService.importProject.promise().resolve(projectResult)
|
||||
|
||||
ctrl.startImport(users).then () ->
|
||||
expect(loaderObj.start).have.been.called
|
||||
expect(loaderObj.stop).have.been.called
|
||||
expect(mocks.asanaService.importProject).have.been.calledWith('project-name', 'project-description', 1, users, false, true)
|
||||
|
||||
done()
|
|
@ -0,0 +1,35 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: asana-import.directive.coffee
|
||||
###
|
||||
|
||||
AsanaImportDirective = () ->
|
||||
return {
|
||||
link: (scope, elm, attrs, ctrl) ->
|
||||
ctrl.startProjectSelector()
|
||||
templateUrl:"projects/create/asana-import/asana-import.html",
|
||||
controller: "AsanaImportCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {
|
||||
onCancel: '&'
|
||||
}
|
||||
}
|
||||
|
||||
AsanaImportDirective.$inject = []
|
||||
|
||||
angular.module("taigaProjects").directive("tgAsanaImport", AsanaImportDirective)
|
|
@ -0,0 +1,31 @@
|
|||
.create-project.import-project(ng-if="vm.step == 'autorization-asana'")
|
||||
p autorization...
|
||||
|
||||
|
||||
tg-import-project-selector(
|
||||
logo="/#{v}/images/import-logos/asana.png"
|
||||
search="{{ 'PROJECT.IMPORT.ASANA.CHOOSE_BOARD' | translate }}"
|
||||
projects="vm.projects"
|
||||
on-cancel="vm.onCancel()"
|
||||
on-select-project="vm.onSelectProject(project)"
|
||||
ng-if="vm.step == 'project-select-asana'"
|
||||
)
|
||||
|
||||
tg-asana-import-project-form(
|
||||
ng-if="vm.step == 'project-form-asana'"
|
||||
project="vm.project"
|
||||
members="vm.members"
|
||||
fetching-users="vm.fetchingUsers"
|
||||
on-save-project-details="vm.onSaveProjectDetails(project)"
|
||||
on-cancel-form="vm.step = 'project-select-asana'"
|
||||
)
|
||||
|
||||
tg-import-project-members(
|
||||
ng-if="vm.step == 'project-members-asana'"
|
||||
platform="Asana"
|
||||
logo="/#{v}/images/import-logos/asana.png"
|
||||
project="vm.project"
|
||||
members="vm.members"
|
||||
on-submit="vm.submitUserSelection(users)"
|
||||
on-cancel="vm.onCancelMemberSelection()"
|
||||
)
|
|
@ -0,0 +1,57 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: asana-import.service.coffee
|
||||
###
|
||||
|
||||
class AsanaImportService extends taiga.Service
|
||||
@.$inject = [
|
||||
'tgResources',
|
||||
'$location'
|
||||
]
|
||||
|
||||
constructor: (@resources, @location) ->
|
||||
@.projects = Immutable.List()
|
||||
@.projectUsers = Immutable.List()
|
||||
@.token = null
|
||||
|
||||
setToken: (token) ->
|
||||
@.token = token
|
||||
|
||||
fetchProjects: () ->
|
||||
@resources.asanaImporter.listProjects(@.token).then (projects) => @.projects = projects
|
||||
|
||||
fetchUsers: (projectId) ->
|
||||
@resources.asanaImporter.listUsers(@.token, projectId).then (users) => @.projectUsers = users
|
||||
|
||||
importProject: (name, description, projectId, userBindings, keepExternalReference, isPrivate, projectType) ->
|
||||
return @resources.asanaImporter.importProject(@.token, name, description, projectId, userBindings, keepExternalReference, isPrivate, projectType)
|
||||
|
||||
getAuthUrl: () ->
|
||||
return new Promise (resolve) =>
|
||||
@resources.asanaImporter.getAuthUrl().then (response) =>
|
||||
@.authUrl = response.data.url
|
||||
resolve(@.authUrl)
|
||||
|
||||
authorize: (code) ->
|
||||
return new Promise (resolve, reject) =>
|
||||
@resources.asanaImporter.authorize(code).then ((response) =>
|
||||
@.token = response.data.token
|
||||
resolve(@.token)
|
||||
), (error) ->
|
||||
reject(new Error(error.status))
|
||||
|
||||
angular.module("taigaProjects").service("tgAsanaImportService", AsanaImportService)
|
|
@ -0,0 +1,128 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: asana-import.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "tgAsanaImportService", ->
|
||||
$provide = null
|
||||
service = null
|
||||
mocks = {}
|
||||
|
||||
_mockResources = ->
|
||||
mocks.resources = {
|
||||
asanaImporter: {
|
||||
listProjects: sinon.stub(),
|
||||
listUsers: sinon.stub(),
|
||||
importProject: sinon.stub(),
|
||||
getAuthUrl: sinon.stub(),
|
||||
authorize: sinon.stub()
|
||||
}
|
||||
}
|
||||
|
||||
$provide.value("tgResources", mocks.resources)
|
||||
|
||||
_mockLocation = ->
|
||||
mocks.location = {
|
||||
search: sinon.stub()
|
||||
}
|
||||
|
||||
mocks.location.search.returns({
|
||||
from: 'asana'
|
||||
token: 123
|
||||
})
|
||||
|
||||
$provide.value("$location", mocks.location)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockResources()
|
||||
_mockLocation()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_tgAsanaImportService_) ->
|
||||
service = _tgAsanaImportService_
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProjects"
|
||||
|
||||
_setup()
|
||||
|
||||
it "fetch projects", (done) ->
|
||||
service.setToken(123)
|
||||
mocks.resources.asanaImporter.listProjects.withArgs(123).promise().resolve('projects')
|
||||
|
||||
service.fetchProjects().then () ->
|
||||
service.projects = "projects"
|
||||
done()
|
||||
|
||||
it "fetch user", (done) ->
|
||||
service.setToken(123)
|
||||
projectId = 3
|
||||
mocks.resources.asanaImporter.listUsers.withArgs(123, projectId).promise().resolve('users')
|
||||
|
||||
service.fetchUsers(projectId).then () ->
|
||||
service.projectUsers = 'users'
|
||||
done()
|
||||
|
||||
it "import project", () ->
|
||||
service.setToken(123)
|
||||
projectId = 2
|
||||
|
||||
service.importProject(projectId, true, true ,true)
|
||||
|
||||
expect(mocks.resources.asanaImporter.importProject).to.have.been.calledWith(123, projectId, true, true, true)
|
||||
|
||||
it "get auth url", (done) ->
|
||||
service.setToken(123)
|
||||
projectId = 3
|
||||
|
||||
response = {
|
||||
data: {
|
||||
url: "url123"
|
||||
}
|
||||
}
|
||||
|
||||
mocks.resources.asanaImporter.getAuthUrl.promise().resolve(response)
|
||||
|
||||
service.getAuthUrl().then (url) ->
|
||||
expect(url).to.be.equal("url123")
|
||||
done()
|
||||
|
||||
it "authorize", (done) ->
|
||||
service.setToken(123)
|
||||
projectId = 3
|
||||
verifyCode = 12345
|
||||
|
||||
response = {
|
||||
data: {
|
||||
token: "token123"
|
||||
}
|
||||
}
|
||||
|
||||
mocks.resources.asanaImporter.authorize.withArgs(verifyCode).promise().resolve(response)
|
||||
|
||||
service.authorize(verifyCode).then (token) ->
|
||||
expect(token).to.be.equal("token123")
|
||||
done()
|
|
@ -0,0 +1,63 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: create-project-form.controller.coffee
|
||||
###
|
||||
|
||||
class CreatetProjectFormController
|
||||
@.$inject = [
|
||||
"tgCurrentUserService",
|
||||
"tgProjectsService",
|
||||
"$projectUrl",
|
||||
"$location",
|
||||
"$tgNavUrls"
|
||||
]
|
||||
|
||||
constructor: (@currentUserService, @projectsService, @projectUrl, @location, @navUrls) ->
|
||||
@.projectForm = {
|
||||
is_private: false
|
||||
}
|
||||
|
||||
@.canCreatePublicProjects = @currentUserService.canCreatePublicProjects()
|
||||
@.canCreatePrivateProjects = @currentUserService.canCreatePrivateProjects()
|
||||
|
||||
if !@.canCreatePublicProjects.valid && @.canCreatePrivateProjects.valid
|
||||
@.projectForm.is_private = true
|
||||
|
||||
if @.type == 'scrum'
|
||||
@.projectForm.creation_template = 1
|
||||
else
|
||||
@.projectForm.creation_template = 2
|
||||
|
||||
submit: () ->
|
||||
@.formSubmitLoading = true
|
||||
|
||||
@projectsService.create(@.projectForm).then (project) =>
|
||||
@location.url(@projectUrl.get(project))
|
||||
|
||||
onCancelForm: () ->
|
||||
@location.path(@navUrls.resolve("create-project"))
|
||||
|
||||
canCreateProject: () ->
|
||||
if @.projectForm.is_private
|
||||
return @.canCreatePrivateProjects.valid
|
||||
else
|
||||
return @.canCreatePublicProjects.valid
|
||||
|
||||
isDisabled: () ->
|
||||
return @.formSubmitLoading || !@.canCreateProject()
|
||||
|
||||
angular.module('taigaProjects').controller('CreateProjectFormCtrl', CreatetProjectFormController)
|
|
@ -0,0 +1,139 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: create-project-form.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "CreateProjectFormCtrl", ->
|
||||
$provide = null
|
||||
$controller = null
|
||||
mocks = {}
|
||||
|
||||
_mockNavUrlsService = ->
|
||||
mocks.navUrls = {
|
||||
resolve: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$tgNavUrls", mocks.navUrls)
|
||||
|
||||
_mockCurrentUserService = ->
|
||||
mocks.currentUserService = {
|
||||
canCreatePublicProjects: sinon.stub().returns({valid: true}),
|
||||
canCreatePrivateProjects: sinon.stub().returns({valid: true})
|
||||
}
|
||||
|
||||
$provide.value("tgCurrentUserService", mocks.currentUserService)
|
||||
|
||||
_mockProjectsService = ->
|
||||
mocks.projectsService = {
|
||||
create: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgProjectsService", mocks.projectsService)
|
||||
|
||||
_mockProjectUrl = ->
|
||||
mocks.projectUrl = {
|
||||
get: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$projectUrl", mocks.projectUrl)
|
||||
|
||||
_mockLocation = ->
|
||||
mocks.location = {
|
||||
url: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$location", mocks.location)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockCurrentUserService()
|
||||
_mockProjectsService()
|
||||
_mockProjectUrl()
|
||||
_mockLocation()
|
||||
_mockNavUrlsService()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_$controller_) ->
|
||||
$controller = _$controller_
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProjects"
|
||||
|
||||
_setup()
|
||||
|
||||
it "submit project form", () ->
|
||||
ctrl = $controller("CreateProjectFormCtrl")
|
||||
|
||||
ctrl.projectForm = 'form'
|
||||
|
||||
mocks.projectsService.create.withArgs('form').promise().resolve('project1')
|
||||
mocks.projectUrl.get.returns('project-url')
|
||||
|
||||
ctrl.submit().then () ->
|
||||
expect(ctrl.formSubmitLoading).to.be.true
|
||||
|
||||
expect(mocks.location.url).to.have.been.calledWith('project-url')
|
||||
|
||||
it 'check if the user can create a private projects', () ->
|
||||
mocks.currentUserService.canCreatePrivateProjects = sinon.stub().returns({valid: true})
|
||||
|
||||
ctrl = $controller("CreateProjectFormCtrl")
|
||||
|
||||
ctrl.projectForm = {
|
||||
is_private: true
|
||||
}
|
||||
|
||||
expect(ctrl.canCreateProject()).to.be.true
|
||||
|
||||
mocks.currentUserService.canCreatePrivateProjects = sinon.stub().returns({valid: false})
|
||||
|
||||
ctrl = $controller("CreateProjectFormCtrl")
|
||||
|
||||
ctrl.projectForm = {
|
||||
is_private: true
|
||||
}
|
||||
|
||||
expect(ctrl.canCreateProject()).to.be.false
|
||||
|
||||
it 'check if the user can create a public projects', () ->
|
||||
mocks.currentUserService.canCreatePublicProjects = sinon.stub().returns({valid: true})
|
||||
|
||||
ctrl = $controller("CreateProjectFormCtrl")
|
||||
|
||||
ctrl.projectForm = {
|
||||
is_private: false
|
||||
}
|
||||
|
||||
expect(ctrl.canCreateProject()).to.be.true
|
||||
|
||||
mocks.currentUserService.canCreatePublicProjects = sinon.stub().returns({valid: false})
|
||||
|
||||
ctrl = $controller("CreateProjectFormCtrl")
|
||||
|
||||
ctrl.projectForm = {
|
||||
is_private: false
|
||||
}
|
||||
|
||||
expect(ctrl.canCreateProject()).to.be.false
|
|
@ -0,0 +1,31 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: create-project-form.directive.coffee
|
||||
###
|
||||
|
||||
CreateProjectFormDirective = () ->
|
||||
return {
|
||||
templateUrl:"projects/create/create-project-form/create-project-form.html",
|
||||
controller: "CreateProjectFormCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {
|
||||
type: '@'
|
||||
}
|
||||
}
|
||||
|
||||
angular.module("taigaProjects").directive("tgCreateProjectForm", CreateProjectFormDirective)
|
|
@ -0,0 +1,32 @@
|
|||
.create-project
|
||||
h1.create-project-title(ng-if="vm.type == 'scrum'")
|
||||
tg-svg(svg-icon="icon-scrum")
|
||||
span(translate="PROJECT.CREATE.TEMPLATE_SCRUM")
|
||||
h3.create-project-description(
|
||||
ng-if="vm.type == 'scrum'"
|
||||
translate="PROJECT.CREATE.TEMPLATE_SCRUM_DESC"
|
||||
)
|
||||
|
||||
h1.create-project-title(ng-if="vm.type == 'kanban'")
|
||||
tg-svg(svg-icon="icon-kanban")
|
||||
span(translate="PROJECT.CREATE.TEMPLATE_KANBAN")
|
||||
h3.create-project-description(
|
||||
ng-if="vm.type == 'kanban'"
|
||||
translate="PROJECT.CREATE.TEMPLATE_KANBAN_DESC"
|
||||
)
|
||||
|
||||
form(
|
||||
name="projectForm",
|
||||
ng-submit="vm.submit()"
|
||||
)
|
||||
div(ng-include="'projects/create/import-project-form-common/name.html'")
|
||||
div(ng-include="'projects/create/import-project-form-common/description.html'")
|
||||
div(ng-include="'projects/create/import-project-form-common/project-privacy.html'")
|
||||
|
||||
tg-create-project-restrictions(
|
||||
is-private="vm.projectForm.is_private"
|
||||
can-create-public-projects="vm.canCreatePublicProjects"
|
||||
can-create-private-projects="vm.canCreatePrivateProjects"
|
||||
)
|
||||
|
||||
div(ng-include="'projects/create/import-project-form-common/actions.html'")
|
|
@ -0,0 +1,13 @@
|
|||
module = angular.module("taigaProject")
|
||||
|
||||
createProjectMembersRestrictionsDirective = () ->
|
||||
return {
|
||||
scope: {
|
||||
isPrivate: '=',
|
||||
limitMembersPrivateProject: '=',
|
||||
limitMembersPublicProject: '='
|
||||
},
|
||||
templateUrl: "projects/create/create-project-members-restrictions/create-project-members-restrictions.html"
|
||||
}
|
||||
|
||||
module.directive('tgCreateProjectMembersRestrictions', [createProjectMembersRestrictionsDirective])
|
|
@ -0,0 +1,13 @@
|
|||
div.create-project-warning(ng-if="!limitMembersPublicProject.valid && !isPrivate")
|
||||
tg-svg(svg-icon="icon-exclamation")
|
||||
span(
|
||||
translate="PROJECT.IMPORT.PROJECT_RESTRICTIONS.PROJECT_MEMBERS_DESC_PUBLIC",
|
||||
translate-values="{'members': limitMembersPublicProject.current, 'max_memberships': limitMembersPublicProject.max}"
|
||||
)
|
||||
|
||||
div.create-project-warning(ng-if="!limitMembersPrivateProject.valid && isPrivate")
|
||||
tg-svg(svg-icon="icon-exclamation")
|
||||
span(
|
||||
translate="PROJECT.IMPORT.PROJECT_RESTRICTIONS.PROJECT_MEMBERS_DESC_PRIVATE",
|
||||
translate-values="{'members': limitMembersPrivateProject.current, 'max_memberships': limitMembersPrivateProject.max}"
|
||||
)
|
|
@ -0,0 +1,13 @@
|
|||
module = angular.module("taigaProject")
|
||||
|
||||
createProjectRestrictionsDirective = () ->
|
||||
return {
|
||||
scope: {
|
||||
isPrivate: '=',
|
||||
canCreatePrivateProjects: '=',
|
||||
canCreatePublicProjects: '='
|
||||
},
|
||||
templateUrl: "projects/create/create-project-restrictions/create-project-restrictions.html"
|
||||
}
|
||||
|
||||
module.directive('tgCreateProjectRestrictions', [createProjectRestrictionsDirective])
|
|
@ -0,0 +1,11 @@
|
|||
div.create-project-warning(
|
||||
ng-if="isPrivate && !canCreatePrivateProjects.valid && canCreatePrivateProjects.reason == 'max_private_projects'"
|
||||
)
|
||||
tg-svg(svg-icon="icon-exclamation")
|
||||
span {{ 'PROJECT.CREATE.MAX_PRIVATE_PROJECTS' | translate }}
|
||||
|
||||
div.create-project-warning(
|
||||
ng-if="!isPrivate && !canCreatePublicProjects.valid && canCreatePublicProjects.reason == 'max_public_projects'"
|
||||
)
|
||||
tg-svg(svg-icon="icon-exclamation")
|
||||
span {{ 'PROJECT.CREATE.MAX_PUBLIC_PROJECTS' | translate }}
|
|
@ -0,0 +1,56 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: project.controller.coffee
|
||||
###
|
||||
|
||||
class CreateProjectController
|
||||
@.$inject = [
|
||||
"tgAppMetaService",
|
||||
"$translate",
|
||||
"tgProjectService",
|
||||
"$location"
|
||||
]
|
||||
|
||||
constructor: (@appMetaService, @translate, @projectService, @location) ->
|
||||
taiga.defineImmutableProperty @, "project", () => return @projectService.project
|
||||
|
||||
@appMetaService.setfn @._setMeta.bind(this)
|
||||
|
||||
@.displayScrumDesc = false
|
||||
@.displayKanbanDesc = false
|
||||
|
||||
_setMeta: () ->
|
||||
return null if !@.project
|
||||
|
||||
ctx = {projectName: @.project.get("name")}
|
||||
|
||||
return {
|
||||
title: @translate.instant("PROJECT.PAGE_TITLE", ctx)
|
||||
description: @.project.get("description")
|
||||
}
|
||||
|
||||
displayHelp: (type, $event) ->
|
||||
$event.stopPropagation()
|
||||
$event.preventDefault()
|
||||
|
||||
if type == 'scrum'
|
||||
@.displayScrumDesc = !@.displayScrumDesc
|
||||
if type == 'kanban'
|
||||
@.displayKanbanDesc = !@.displayKanbanDesc
|
||||
|
||||
|
||||
angular.module("taigaProjects").controller("CreateProjectCtrl", CreateProjectController)
|
|
@ -0,0 +1,45 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: create-project.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "CreateProjectController", ->
|
||||
provide = null
|
||||
controller = null
|
||||
mocks = {}
|
||||
|
||||
_inject = (callback) ->
|
||||
inject (_$controller_, _$q_, _$rootScope_) ->
|
||||
controller = _$controller_
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProjects"
|
||||
_inject()
|
||||
|
||||
# it "get Home Step", () ->
|
||||
# ctrl = controller "CreateProjectCtrl"
|
||||
# ctrl.inDefaultStep = true
|
||||
# ctrl.getStep('home')
|
||||
# expect(ctrl.inDefaultStep).to.be.true
|
||||
# expect(ctrl.inStepDuplicateProject).to.be.false
|
||||
#
|
||||
# it "get Duplicate Project Step", () ->
|
||||
# ctrl = controller "CreateProjectCtrl"
|
||||
# ctrl.inDefaultStep = true
|
||||
# ctrl.getStep('duplicate')
|
||||
# expect(ctrl.inDefaultStep).to.be.false
|
||||
# expect(ctrl.inStepDuplicateProject).to.be.true
|
|
@ -0,0 +1,69 @@
|
|||
.create-project
|
||||
.create-project-wrapper
|
||||
h1.create-project-title(translate="PROJECT.CREATE.TITLE")
|
||||
h3.create-project-description(translate="PROJECT.CREATE.CHOOSE_TEMPLATE")
|
||||
ul.create-project-selector.e2e-create-project-selector
|
||||
li
|
||||
a.e2e-create-project-scrum(
|
||||
title="{{'PROJECT.CREATE.TEMPLATE_SCRUM' | translate}}",
|
||||
tg-nav="create-project-scrum"
|
||||
href=""
|
||||
)
|
||||
.create-project-selector-icon
|
||||
tg-svg(svg-icon="icon-scrum")
|
||||
.create-project-selector-template-wrapper
|
||||
p.create-project-selector-template(translate="PROJECT.CREATE.TEMPLATE_SCRUM")
|
||||
p.create-project-selector-description(translate="PROJECT.CREATE.TEMPLATE_SCRUM_DESC")
|
||||
.create-project-selector-question
|
||||
tg-svg(
|
||||
svg-icon="icon-question"
|
||||
ng-click="vm.displayHelp('scrum', $event)"
|
||||
)
|
||||
.create-project-selector-long-description(ng-show="vm.displayScrumDesc")
|
||||
p(translate="PROJECT.CREATE.TEMPLATE_SCRUM_LONGDESC")
|
||||
li
|
||||
a.e2e-create-project-kanban(
|
||||
tg-nav="create-project-kanban"
|
||||
title="{{'PROJECT.CREATE.TEMPLATE_KANBAN' | translate}}",
|
||||
href=""
|
||||
)
|
||||
.create-project-selector-icon
|
||||
tg-svg(svg-icon="icon-kanban")
|
||||
|
||||
.create-project-selector-template-wrapper
|
||||
p.create-project-selector-template(translate="PROJECT.CREATE.TEMPLATE_KANBAN")
|
||||
p.create-project-selector-description(translate="PROJECT.CREATE.TEMPLATE_KANBAN_DESC")
|
||||
.create-project-selector-question
|
||||
tg-svg(
|
||||
svg-icon="icon-question"
|
||||
ng-click="vm.displayHelp('kanban', $event)"
|
||||
)
|
||||
.create-project-selector-long-description(ng-show="vm.displayKanbanDesc")
|
||||
p(translate="PROJECT.CREATE.TEMPLATE_KANBAN_LONGDESC")
|
||||
li
|
||||
a.e2e-duplicate-project(
|
||||
tg-nav="create-project-duplicate"
|
||||
title="{{'PROJECT.CREATE.DUPLICATE' | translate}}",
|
||||
href=""
|
||||
)
|
||||
.create-project-selector-icon
|
||||
tg-svg(svg-icon="icon-duplicate")
|
||||
.create-project-selector-template-wrapper
|
||||
p.create-project-selector-template(translate="PROJECT.CREATE.DUPLICATE")
|
||||
p.create-project-selector-description(translate="PROJECT.CREATE.DUPLICATE_DESC")
|
||||
|
||||
li
|
||||
a(
|
||||
tg-nav="create-project-import"
|
||||
title="{{'PROJECT.CREATE.IMPORT' | translate}}",
|
||||
href=""
|
||||
)
|
||||
.create-project-selector-icon
|
||||
tg-svg(svg-icon="icon-upload")
|
||||
.create-project-selector-template-wrapper
|
||||
p.create-project-selector-template(
|
||||
href="#"
|
||||
title="{{'PROJECT.CREATE.IMPORT' | translate}}",
|
||||
translate="PROJECT.CREATE.IMPORT"
|
||||
)
|
||||
p.create-project-selector-description(translate="PROJECT.CREATE.IMPORT_DESC")
|
|
@ -0,0 +1,3 @@
|
|||
.create-project {
|
||||
@include create-project;
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: project.controller.coffee
|
||||
###
|
||||
|
||||
class DuplicateProjectController
|
||||
@.$inject = [
|
||||
"tgCurrentUserService",
|
||||
"tgProjectsService",
|
||||
"$tgLocation",
|
||||
"$tgNavUrls"
|
||||
]
|
||||
|
||||
constructor: (@currentUserService, @projectsService, @location, @navUrls) ->
|
||||
@.user = @currentUserService.getUser()
|
||||
@.members = Immutable.List()
|
||||
|
||||
@.canCreatePublicProjects = @currentUserService.canCreatePublicProjects()
|
||||
@.canCreatePrivateProjects = @currentUserService.canCreatePrivateProjects()
|
||||
|
||||
taiga.defineImmutableProperty @, 'projects', () => @currentUserService.projects.get("all")
|
||||
|
||||
@.projectForm = {
|
||||
is_private: false
|
||||
}
|
||||
|
||||
if !@.canCreatePublicProjects.valid && @.canCreatePrivateProjects.valid
|
||||
@.projectForm.is_private = true
|
||||
|
||||
refreshReferenceProject: (slug) ->
|
||||
@projectsService.getProjectBySlug(slug).then (project) =>
|
||||
@.referenceProject = project
|
||||
@.members = project.get('members')
|
||||
@.invitedMembers = @.members.map (it) -> return it.get('id')
|
||||
@.checkUsersLimit()
|
||||
|
||||
toggleInvitedMember: (member) ->
|
||||
if @.invitedMembers.includes(member)
|
||||
@.invitedMembers = @.invitedMembers.filter (it) -> it != member
|
||||
else
|
||||
@.invitedMembers = @.invitedMembers.push(member)
|
||||
|
||||
@.checkUsersLimit()
|
||||
|
||||
checkUsersLimit: () ->
|
||||
@.limitMembersPrivateProject = @currentUserService.canAddMembersPrivateProject(@.invitedMembers.size + 1)
|
||||
@.limitMembersPublicProject = @currentUserService.canAddMembersPublicProject(@.invitedMembers.size + 1)
|
||||
|
||||
submit: () ->
|
||||
projectId = @.referenceProject.get('id')
|
||||
data = @.projectForm
|
||||
|
||||
@.formSubmitLoading = true
|
||||
@projectsService.duplicate(projectId, data).then (newProject) =>
|
||||
@.formSubmitLoading = false
|
||||
@location.path(@navUrls.resolve("project", {project: newProject.data.slug}))
|
||||
@currentUserService.loadProjects()
|
||||
|
||||
canCreateProject: () ->
|
||||
if @.projectForm.is_private
|
||||
return @.canCreatePrivateProjects.valid && @.limitMembersPrivateProject.valid
|
||||
else
|
||||
return @.canCreatePublicProjects.valid && @.limitMembersPublicProject.valid
|
||||
|
||||
isDisabled: () ->
|
||||
return @.formSubmitLoading || !@.canCreateProject()
|
||||
|
||||
onCancelForm: () ->
|
||||
@location.path(@navUrls.resolve("create-project"))
|
||||
|
||||
angular.module("taigaProjects").controller("DuplicateProjectCtrl", DuplicateProjectController)
|
|
@ -0,0 +1,222 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: home.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "DuplicateProjectController", ->
|
||||
ctrl = null
|
||||
provide = null
|
||||
controller = null
|
||||
mocks = {}
|
||||
|
||||
_mockCurrentUserService = () ->
|
||||
mocks.currentUserService = {}
|
||||
mocks.currentUserService.getUser = sinon.stub()
|
||||
mocks.currentUserService.canCreatePublicProjects = sinon.stub().returns(true)
|
||||
mocks.currentUserService.canCreatePrivateProjects = sinon.stub().returns(true)
|
||||
|
||||
mocks.currentUserService.projects = {}
|
||||
mocks.currentUserService.projects.get = sinon.stub().returns([])
|
||||
|
||||
mocks.currentUserService.loadProjects = sinon.stub()
|
||||
|
||||
mocks.currentUserService.canAddMembersPrivateProject = sinon.stub()
|
||||
mocks.currentUserService.canAddMembersPublicProject = sinon.stub()
|
||||
|
||||
provide.value "tgCurrentUserService", mocks.currentUserService
|
||||
|
||||
_mockProjectService = () ->
|
||||
mocks.projectsService = {}
|
||||
mocks.projectsService.getProjectBySlug = sinon.stub()
|
||||
mocks.projectsService.duplicate = sinon.stub()
|
||||
|
||||
provide.value "tgProjectsService", mocks.projectsService
|
||||
|
||||
_mockLocation = () ->
|
||||
mocks.location = {
|
||||
path: sinon.stub()
|
||||
}
|
||||
provide.value "$tgLocation", mocks.location
|
||||
|
||||
_mockTgNav = () ->
|
||||
mocks.urlservice = {
|
||||
resolve: sinon.stub()
|
||||
}
|
||||
provide.value "$tgNavUrls", mocks.urlservice
|
||||
|
||||
_mocks = () ->
|
||||
module ($provide) ->
|
||||
provide = $provide
|
||||
_mockCurrentUserService()
|
||||
_mockProjectService()
|
||||
_mockLocation()
|
||||
_mockTgNav()
|
||||
|
||||
return null
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProjects"
|
||||
|
||||
_mocks()
|
||||
|
||||
inject ($controller) ->
|
||||
controller = $controller
|
||||
|
||||
ctrl = controller "DuplicateProjectCtrl"
|
||||
|
||||
ctrl.projects = Immutable.fromJS([
|
||||
{
|
||||
id: 1
|
||||
},
|
||||
{
|
||||
id: 2
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
|
||||
ctrl.canCreatePublicProjects = mocks.currentUserService.canCreatePublicProjects()
|
||||
ctrl.canCreatePrivateProjects = mocks.currentUserService.canCreatePublicProjects()
|
||||
ctrl.projectForm = {}
|
||||
|
||||
it "toggle invited Member", () ->
|
||||
ctrl = controller "DuplicateProjectCtrl"
|
||||
|
||||
ctrl.invitedMembers = Immutable.List([1, 2, 3])
|
||||
ctrl.checkUsersLimit = sinon.spy()
|
||||
|
||||
ctrl.toggleInvitedMember(2)
|
||||
|
||||
expect(ctrl.invitedMembers.toJS()).to.be.eql([1, 3])
|
||||
|
||||
ctrl.toggleInvitedMember(5)
|
||||
|
||||
expect(ctrl.invitedMembers.toJS()).to.be.eql([1, 3, 5])
|
||||
|
||||
expect(ctrl.checkUsersLimit).to.have.been.called
|
||||
|
||||
it "get project to duplicate", () ->
|
||||
project = Immutable.fromJS({
|
||||
members: [
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
]
|
||||
})
|
||||
|
||||
slug = 'slug'
|
||||
ctrl._getInvitedMembers = sinon.stub()
|
||||
|
||||
promise = mocks.projectsService.getProjectBySlug.withArgs(slug).promise().resolve(project)
|
||||
|
||||
ctrl.refreshReferenceProject(slug).then () ->
|
||||
expect(ctrl.referenceProject).to.be.equal(project)
|
||||
expect(ctrl.members).to.be.equal(project.get('members'))
|
||||
expect(ctrl.invitedMembers.toJS()).to.be.eql([1, 2, 3])
|
||||
|
||||
it 'check users limits', () ->
|
||||
mocks.currentUserService.canAddMembersPrivateProject.withArgs(4).returns(1)
|
||||
mocks.currentUserService.canAddMembersPublicProject.withArgs(4).returns(2)
|
||||
|
||||
members = Immutable.fromJS([
|
||||
{id: 1},
|
||||
{id: 2},
|
||||
{id: 3}
|
||||
])
|
||||
size = members.size #3
|
||||
|
||||
ctrl.user = Immutable.fromJS({
|
||||
max_memberships_public_projects: 1
|
||||
max_memberships_private_projects: 1
|
||||
})
|
||||
|
||||
ctrl.projectForm = {}
|
||||
ctrl.projectForm.is_private = false
|
||||
ctrl.invitedMembers = members
|
||||
|
||||
ctrl.checkUsersLimit()
|
||||
expect(ctrl.limitMembersPrivateProject).to.be.equal(1)
|
||||
expect(ctrl.limitMembersPublicProject).to.be.equal(2)
|
||||
|
||||
it 'duplicate project', (done) ->
|
||||
ctrl.referenceProject = Immutable.fromJS({
|
||||
id: 1
|
||||
})
|
||||
ctrl.projectForm = Immutable.fromJS({
|
||||
id: 1
|
||||
})
|
||||
projectId = ctrl.referenceProject.get('id')
|
||||
data = ctrl.projectForm
|
||||
|
||||
newProject = {}
|
||||
newProject.data = {
|
||||
slug: 'slug'
|
||||
}
|
||||
|
||||
mocks.urlservice.resolve.withArgs("project", {project: newProject.data.slug}).returns("/project/slug/")
|
||||
|
||||
promise = mocks.projectsService.duplicate.withArgs(projectId, data).promise().resolve(newProject)
|
||||
|
||||
ctrl.submit().then () ->
|
||||
expect(ctrl.formSubmitLoading).to.be.false
|
||||
expect(mocks.location.path).to.be.calledWith("/project/slug/")
|
||||
expect(mocks.currentUserService.loadProjects).to.have.been.called
|
||||
done()
|
||||
|
||||
it 'check if the user can create a private projects', () ->
|
||||
mocks.currentUserService.canCreatePrivateProjects = sinon.stub().returns({valid: true})
|
||||
|
||||
ctrl = controller "DuplicateProjectCtrl"
|
||||
ctrl.limitMembersPrivateProject = {valid: true}
|
||||
|
||||
ctrl.projectForm = {
|
||||
is_private: true
|
||||
}
|
||||
|
||||
expect(ctrl.canCreateProject()).to.be.true
|
||||
|
||||
mocks.currentUserService.canCreatePrivateProjects = sinon.stub().returns({valid: false})
|
||||
|
||||
ctrl = controller "DuplicateProjectCtrl"
|
||||
|
||||
ctrl.projectForm = {
|
||||
is_private: true
|
||||
}
|
||||
|
||||
expect(ctrl.canCreateProject()).to.be.false
|
||||
|
||||
it 'check if the user can create a public projects', () ->
|
||||
mocks.currentUserService.canCreatePublicProjects = sinon.stub().returns({valid: true})
|
||||
|
||||
ctrl = controller "DuplicateProjectCtrl"
|
||||
ctrl.limitMembersPublicProject = {valid: true}
|
||||
|
||||
ctrl.projectForm = {
|
||||
is_private: false
|
||||
}
|
||||
|
||||
expect(ctrl.canCreateProject()).to.be.true
|
||||
|
||||
mocks.currentUserService.canCreatePublicProjects = sinon.stub().returns({valid: false})
|
||||
|
||||
ctrl = controller "DuplicateProjectCtrl"
|
||||
|
||||
ctrl.projectForm = {
|
||||
is_private: false
|
||||
}
|
||||
|
||||
expect(ctrl.canCreateProject()).to.be.false
|
|
@ -0,0 +1,35 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: duplicate-project.directive.coffee
|
||||
###
|
||||
|
||||
DuplicateProjectDirective = () ->
|
||||
|
||||
link = (scope, el, attr, ctrl) ->
|
||||
|
||||
return {
|
||||
link: link,
|
||||
templateUrl:"projects/create/duplicate/duplicate-project.html",
|
||||
controller: "DuplicateProjectCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {}
|
||||
}
|
||||
|
||||
DuplicateProjectDirective.$inject = []
|
||||
|
||||
angular.module("taigaProjects").directive("tgDuplicateProject", DuplicateProjectDirective)
|
|
@ -0,0 +1,57 @@
|
|||
.create-project
|
||||
h1.create-project-title(translate="PROJECT.DUPLICATE.TITLE")
|
||||
h3.create-project-description(translate="PROJECT.DUPLICATE.DESCRIPTION")
|
||||
form.duplicate-project.e2e-duplicate-project(
|
||||
name="projectForm"
|
||||
ng-submit="vm.submit()"
|
||||
)
|
||||
fieldset.duplicate-project-reference.e2e-duplicate-project-reference
|
||||
select(
|
||||
ng-model="vm.projectForm.project"
|
||||
ng-change="vm.refreshReferenceProject(vm.projectForm.project)"
|
||||
data-required="true"
|
||||
ng-options="p.slug as p.name for p in vm.projects | toMutable| filter:{blocked_code: '!'}"
|
||||
id="project-selector-dropdown"
|
||||
autofocus
|
||||
)
|
||||
option(
|
||||
value=""
|
||||
disabled
|
||||
selected
|
||||
translate="PROJECT.DUPLICATE.SELECT_PLACEHOLDER"
|
||||
)
|
||||
|
||||
div(ng-include="'projects/create/import-project-form-common/name.html'")
|
||||
div(ng-include="'projects/create/import-project-form-common/description.html'")
|
||||
|
||||
div(ng-include="'projects/create/import-project-form-common/project-privacy.html'")
|
||||
tg-create-project-restrictions(
|
||||
is-private="vm.projectForm.is_private"
|
||||
can-create-public-projects="vm.canCreatePublicProjects"
|
||||
can-create-private-projects="vm.canCreatePrivateProjects"
|
||||
)
|
||||
|
||||
|
||||
label(ng-if="vm.invitedMembers")
|
||||
span(translate="PROJECT.CREATE.INVITE")
|
||||
span.mumble(
|
||||
ng-if="vm.displayUserWarning"
|
||||
translate="PROJECT.CREATE.SOLO_PROJECT"
|
||||
)
|
||||
span.mumble(translate="PROJECT.CREATE.INVITE_LATER")
|
||||
|
||||
tg-invite-members(
|
||||
ng-if="vm.members.size"
|
||||
members="vm.members"
|
||||
invited-members="vm.invitedMembers"
|
||||
on-toggle-invited-member="vm.toggleInvitedMember(member)"
|
||||
)
|
||||
|
||||
tg-create-project-members-restrictions(
|
||||
ng-if="vm.referenceProject"
|
||||
is-private="vm.projectForm.is_private"
|
||||
limit-members-private-project="vm.limitMembersPrivateProject"
|
||||
limit-members-public-project="vm.limitMembersPublicProject"
|
||||
)
|
||||
|
||||
div(ng-include="'projects/create/import-project-form-common/actions.html'")
|
|
@ -0,0 +1,5 @@
|
|||
.duplicate-project {
|
||||
&-reference {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: github-import-project-form.controller.coffee
|
||||
###
|
||||
|
||||
class GithubImportProjectFormController
|
||||
@.$inject = [
|
||||
"tgCurrentUserService"
|
||||
]
|
||||
|
||||
constructor: (@currentUserService) ->
|
||||
@.canCreatePublicProjects = @currentUserService.canCreatePublicProjects()
|
||||
@.canCreatePrivateProjects = @currentUserService.canCreatePrivateProjects()
|
||||
|
||||
@.projectForm = @.project.toJS()
|
||||
|
||||
@.platformName = "Github"
|
||||
@.projectForm.is_private = false
|
||||
@.projectForm.keepExternalReference = false
|
||||
@.projectForm.project_type = "kanban"
|
||||
|
||||
if !@.canCreatePublicProjects.valid && @.canCreatePrivateProjects.valid
|
||||
@.projectForm.is_private = true
|
||||
|
||||
checkUsersLimit: () ->
|
||||
@.limitMembersPrivateProject = @currentUserService.canAddMembersPrivateProject(@.members.size)
|
||||
@.limitMembersPublicProject = @currentUserService.canAddMembersPublicProject(@.members.size)
|
||||
|
||||
saveForm: () ->
|
||||
@.onSaveProjectDetails({project: Immutable.fromJS(@.projectForm)})
|
||||
|
||||
canCreateProject: () ->
|
||||
if @.projectForm.is_private
|
||||
return @.canCreatePrivateProjects.valid
|
||||
else
|
||||
return @.canCreatePublicProjects.valid
|
||||
|
||||
isDisabled: () ->
|
||||
return !@.canCreateProject()
|
||||
|
||||
|
||||
angular.module('taigaProjects').controller('GithubImportProjectFormCtrl', GithubImportProjectFormController)
|
|
@ -0,0 +1,40 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: github-import-project-form.directive.coffee
|
||||
###
|
||||
|
||||
GithubImportProjectFormDirective = () ->
|
||||
return {
|
||||
link: (scope, elm, attr, ctrl) ->
|
||||
scope.$watch('vm.members', ctrl.checkUsersLimit.bind(ctrl))
|
||||
|
||||
templateUrl:"projects/create/github-import/github-import-project-form/github-import-project-form.html",
|
||||
controller: "GithubImportProjectFormCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {
|
||||
members: '<',
|
||||
project: '<',
|
||||
onSaveProjectDetails: '&',
|
||||
onCancelForm: '&',
|
||||
fetchingUsers: '<'
|
||||
}
|
||||
}
|
||||
|
||||
GithubImportProjectFormDirective.$inject = []
|
||||
|
||||
angular.module("taigaProjects").directive("tgGithubImportProjectForm", GithubImportProjectFormDirective)
|
|
@ -0,0 +1,71 @@
|
|||
.import-project-github-form
|
||||
div(ng-include="'projects/create/import/import-header.html'")
|
||||
|
||||
.spin(tg-loading="vm.fetchingUsers")
|
||||
|
||||
form(
|
||||
ng-if="!vm.fetchingUsers",
|
||||
name="projectForm",
|
||||
ng-submit="vm.saveForm()"
|
||||
)
|
||||
div(ng-include="'projects/create/import-project-form-common/name.html'")
|
||||
div(ng-include="'projects/create/import-project-form-common/description.html'")
|
||||
|
||||
.create-project-github-import-type(role="group")
|
||||
p.question(translate="PROJECT.IMPORT.GITHUB.HOW_DO_YOU_WANT_TO_IMPORT")
|
||||
.create-project-github-import-type-question
|
||||
fieldset
|
||||
input(
|
||||
type="radio"
|
||||
name="project_type"
|
||||
id="template-issues"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="'issues'"
|
||||
ng-model="vm.projectForm.project_type"
|
||||
required
|
||||
)
|
||||
label(for="template-issues")
|
||||
span.create-project-github-import-type-name(translate="PROJECT.IMPORT.GITHUB.ISSUES_PROJECT")
|
||||
p.create-project-github-import-type-description(translate="PROJECT.IMPORT.GITHUB.ISSUES_PROJECT_DESCRIPTION")
|
||||
fieldset
|
||||
input(
|
||||
type="radio"
|
||||
name="project_type"
|
||||
id="template-scrum"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="'scrum'"
|
||||
ng-model="vm.projectForm.project_type"
|
||||
required
|
||||
)
|
||||
label(for="template-scrum")
|
||||
span.create-project-github-import-type-name(translate="PROJECT.IMPORT.GITHUB.SCRUM_PROJECT")
|
||||
p.create-project-github-import-type-description(translate="PROJECT.IMPORT.GITHUB.SCRUM_PROJECT_DESCRIPTION")
|
||||
fieldset
|
||||
input(
|
||||
type="radio"
|
||||
name="project_type"
|
||||
id="template-kanban"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="'kanban'"
|
||||
ng-model="vm.projectForm.project_type"
|
||||
required
|
||||
)
|
||||
label(for="template-kanban")
|
||||
span.create-project-github-import-type-name(translate="PROJECT.IMPORT.GITHUB.KANBAN_PROJECT")
|
||||
p.create-project-github-import-type-description(translate="PROJECT.IMPORT.GITHUB.KANBAN_PROJECT_DESCRIPTION")
|
||||
div(ng-include="'projects/create/import-project-form-common/project-privacy.html'")
|
||||
tg-create-project-restrictions(
|
||||
is-private="vm.projectForm.is_private"
|
||||
can-create-public-projects="vm.canCreatePublicProjects"
|
||||
can-create-private-projects="vm.canCreatePrivateProjects"
|
||||
)
|
||||
tg-create-project-members-restrictions(
|
||||
is-private="vm.projectForm.is_private"
|
||||
limit-members-private-project="vm.limitMembersPrivateProject"
|
||||
limit-members-public-project="vm.limitMembersPublicProject"
|
||||
)
|
||||
div(ng-include="'projects/create/import-project-form-common/links.html'")
|
||||
div(ng-include="'projects/create/import-project-form-common/actions.html'")
|
|
@ -0,0 +1,61 @@
|
|||
.import-project-github-form {
|
||||
@include create-project;
|
||||
}
|
||||
|
||||
.create-project-github-import-type {
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
&-question {
|
||||
align-content: stretch;
|
||||
align-items: stretch;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
background: $white;
|
||||
border-right: 1px solid $whitish;
|
||||
transition: background .2s linear;
|
||||
|
||||
&:last-child {
|
||||
border: 0;
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
display: none;
|
||||
|
||||
&:checked {
|
||||
+label {
|
||||
background: rgba($primary, .1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
background: $white;
|
||||
height: 100%;
|
||||
padding: 1rem;
|
||||
transition: background .2s ease-in;
|
||||
|
||||
&:hover {
|
||||
background: rgba($primary, .1);
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
&-name {
|
||||
@include font-type(normal);
|
||||
display: inline-block;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
&-description {
|
||||
@include font-type(light);
|
||||
@include font-size(small);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: github-import.controller.coffee
|
||||
###
|
||||
|
||||
class GithubImportController
|
||||
@.$inject = [
|
||||
'tgGithubImportService',
|
||||
'$tgConfirm',
|
||||
'$translate',
|
||||
'tgImportProjectService',
|
||||
]
|
||||
|
||||
constructor: (@githubImportService, @confirm, @translate, @importProjectService) ->
|
||||
@.step = 'autorization-github'
|
||||
@.project = null
|
||||
|
||||
taiga.defineImmutableProperty @, 'projects', () => return @githubImportService.projects
|
||||
taiga.defineImmutableProperty @, 'members', () => return @githubImportService.projectUsers
|
||||
|
||||
startProjectSelector: () ->
|
||||
@.step = 'project-select-github'
|
||||
@githubImportService.fetchProjects()
|
||||
|
||||
onSelectProject: (project) ->
|
||||
@.step = 'project-form-github'
|
||||
@.project = project
|
||||
@.fetchingUsers = true
|
||||
|
||||
@githubImportService.fetchUsers(@.project.get('id')).then () => @.fetchingUsers = false
|
||||
|
||||
onSaveProjectDetails: (project) ->
|
||||
@.project = project
|
||||
@.step = 'project-members-github'
|
||||
|
||||
onCancelMemberSelection: () ->
|
||||
@.step = 'project-form-github'
|
||||
|
||||
startImport: (users) ->
|
||||
loader = @confirm.loader(@translate.instant('PROJECT.IMPORT.IN_PROGRESS.TITLE'), @translate.instant('PROJECT.IMPORT.IN_PROGRESS.DESCRIPTION'), true)
|
||||
|
||||
loader.start()
|
||||
|
||||
promise = @githubImportService.importProject(
|
||||
@.project.get('name'),
|
||||
@.project.get('description'),
|
||||
@.project.get('id'),
|
||||
users,
|
||||
@.project.get('keepExternalReference'),
|
||||
@.project.get('is_private')
|
||||
@.project.get('project_type')
|
||||
)
|
||||
|
||||
@importProjectService.importPromise(promise).then () => loader.stop()
|
||||
|
||||
submitUserSelection: (users) ->
|
||||
@.startImport(users)
|
||||
return null
|
||||
|
||||
angular.module('taigaProjects').controller('GithubImportCtrl', GithubImportController)
|
|
@ -0,0 +1,172 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: github-import.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "GithubImportCtrl", ->
|
||||
$provide = null
|
||||
$controller = null
|
||||
mocks = {}
|
||||
|
||||
_mockCurrentUserService = ->
|
||||
mocks.currentUserService = {
|
||||
canAddMembersPrivateProject: sinon.stub()
|
||||
canAddMembersPublicProject: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgCurrentUserService", mocks.currentUserService)
|
||||
|
||||
_mockImportProjectService = ->
|
||||
mocks.importProjectService = {
|
||||
importPromise: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgImportProjectService", mocks.importProjectService)
|
||||
|
||||
_mockGithubImportService = ->
|
||||
mocks.githubService = {
|
||||
fetchProjects: sinon.stub(),
|
||||
fetchUsers: sinon.stub(),
|
||||
importProject: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgGithubImportService", mocks.githubService)
|
||||
|
||||
_mockConfirm = ->
|
||||
mocks.confirm = {
|
||||
loader: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$tgConfirm", mocks.confirm)
|
||||
|
||||
_mockTranslate = ->
|
||||
mocks.translate = {
|
||||
instant: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$translate", mocks.translate)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockGithubImportService()
|
||||
_mockConfirm()
|
||||
_mockTranslate()
|
||||
_mockImportProjectService()
|
||||
_mockCurrentUserService()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_$controller_) ->
|
||||
$controller = _$controller_
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProjects"
|
||||
|
||||
_setup()
|
||||
|
||||
it "start project selector", () ->
|
||||
ctrl = $controller("GithubImportCtrl")
|
||||
ctrl.startProjectSelector()
|
||||
|
||||
expect(ctrl.step).to.be.equal('project-select-github')
|
||||
expect(mocks.githubService.fetchProjects).have.been.called
|
||||
|
||||
it "on select project reload projects", (done) ->
|
||||
project = Immutable.fromJS({
|
||||
id: 1,
|
||||
name: "project-name"
|
||||
})
|
||||
|
||||
mocks.githubService.fetchUsers.promise().resolve()
|
||||
|
||||
ctrl = $controller("GithubImportCtrl")
|
||||
|
||||
promise = ctrl.onSelectProject(project)
|
||||
|
||||
expect(ctrl.fetchingUsers).to.be.true
|
||||
|
||||
promise.then () ->
|
||||
expect(ctrl.fetchingUsers).to.be.false
|
||||
expect(ctrl.step).to.be.equal('project-form-github')
|
||||
expect(ctrl.project).to.be.equal(project)
|
||||
done()
|
||||
|
||||
it "on save project details reload users", () ->
|
||||
project = Immutable.fromJS({
|
||||
id: 1,
|
||||
name: "project-name"
|
||||
})
|
||||
|
||||
ctrl = $controller("GithubImportCtrl")
|
||||
ctrl.onSaveProjectDetails(project)
|
||||
|
||||
expect(ctrl.step).to.be.equal('project-members-github')
|
||||
expect(ctrl.project).to.be.equal(project)
|
||||
|
||||
it "on select user init import", (done) ->
|
||||
users = Immutable.fromJS([
|
||||
{
|
||||
id: 0
|
||||
},
|
||||
{
|
||||
id: 1
|
||||
},
|
||||
{
|
||||
id: 2
|
||||
}
|
||||
])
|
||||
|
||||
loaderObj = {
|
||||
start: sinon.spy(),
|
||||
update: sinon.stub(),
|
||||
stop: sinon.spy()
|
||||
}
|
||||
|
||||
projectResult = {
|
||||
id: 3,
|
||||
name: "name"
|
||||
}
|
||||
|
||||
mocks.confirm.loader.returns(loaderObj)
|
||||
|
||||
mocks.importProjectService.importPromise.promise().resolve()
|
||||
|
||||
ctrl = $controller("GithubImportCtrl")
|
||||
ctrl.project = Immutable.fromJS({
|
||||
id: 1,
|
||||
name: 'project-name',
|
||||
description: 'project-description',
|
||||
keepExternalReference: false,
|
||||
is_private: true
|
||||
})
|
||||
|
||||
|
||||
mocks.githubService.importProject.promise().resolve(projectResult)
|
||||
|
||||
ctrl.startImport(users).then () ->
|
||||
expect(loaderObj.start).have.been.called
|
||||
expect(loaderObj.stop).have.been.called
|
||||
expect(mocks.githubService.importProject).have.been.calledWith('project-name', 'project-description', 1, users, false, true)
|
||||
|
||||
done()
|
|
@ -0,0 +1,35 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: github-import.directive.coffee
|
||||
###
|
||||
|
||||
GithubImportDirective = () ->
|
||||
return {
|
||||
link: (scope, elm, attrs, ctrl) ->
|
||||
ctrl.startProjectSelector()
|
||||
templateUrl:"projects/create/github-import/github-import.html",
|
||||
controller: "GithubImportCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {
|
||||
onCancel: '&'
|
||||
}
|
||||
}
|
||||
|
||||
GithubImportDirective.$inject = []
|
||||
|
||||
angular.module("taigaProjects").directive("tgGithubImport", GithubImportDirective)
|
|
@ -0,0 +1,31 @@
|
|||
.create-project.import-project(ng-if="vm.step == 'autorization-github'")
|
||||
p autorization...
|
||||
|
||||
|
||||
tg-import-project-selector(
|
||||
logo="/#{v}/images/import-logos/github.png"
|
||||
search="{{ 'PROJECT.IMPORT.GITHUB.CHOOSE_BOARD' | translate }}"
|
||||
projects="vm.projects"
|
||||
on-cancel="vm.onCancel()"
|
||||
on-select-project="vm.onSelectProject(project)"
|
||||
ng-if="vm.step == 'project-select-github'"
|
||||
)
|
||||
|
||||
tg-github-import-project-form(
|
||||
ng-if="vm.step == 'project-form-github'"
|
||||
project="vm.project"
|
||||
members="vm.members"
|
||||
fetching-users="vm.fetchingUsers"
|
||||
on-save-project-details="vm.onSaveProjectDetails(project)"
|
||||
on-cancel-form="vm.step = 'project-select-github'"
|
||||
)
|
||||
|
||||
tg-import-project-members(
|
||||
ng-if="vm.step == 'project-members-github'"
|
||||
platform="Github"
|
||||
logo="/#{v}/images/import-logos/github.png"
|
||||
project="vm.project"
|
||||
members="vm.members"
|
||||
on-submit="vm.submitUserSelection(users)"
|
||||
on-cancel="vm.onCancelMemberSelection()"
|
||||
)
|
|
@ -0,0 +1,55 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: github-import.service.coffee
|
||||
###
|
||||
|
||||
class GithubImportService extends taiga.Service
|
||||
@.$inject = [
|
||||
'tgResources'
|
||||
]
|
||||
|
||||
constructor: (@resources, @location) ->
|
||||
@.projects = Immutable.List()
|
||||
@.projectUsers = Immutable.List()
|
||||
|
||||
setToken: (token) ->
|
||||
@.token = token
|
||||
|
||||
fetchProjects: () ->
|
||||
@resources.githubImporter.listProjects(@.token).then (projects) => @.projects = projects
|
||||
|
||||
fetchUsers: (projectId) ->
|
||||
@resources.githubImporter.listUsers(@.token, projectId).then (users) => @.projectUsers = users
|
||||
|
||||
importProject: (name, description, projectId, userBindings, keepExternalReference, isPrivate, projectType) ->
|
||||
return @resources.githubImporter.importProject(@.token, name, description, projectId, userBindings, keepExternalReference, isPrivate, projectType)
|
||||
|
||||
getAuthUrl: (callbackUri) ->
|
||||
return new Promise (resolve) =>
|
||||
@resources.githubImporter.getAuthUrl(callbackUri).then (response) =>
|
||||
@.authUrl = response.data.url
|
||||
resolve(@.authUrl)
|
||||
|
||||
authorize: (code) ->
|
||||
return new Promise (resolve, reject) =>
|
||||
@resources.githubImporter.authorize(code).then ((response) =>
|
||||
@.token = response.data.token
|
||||
resolve(@.token)
|
||||
), (error) ->
|
||||
reject(new Error(error.status))
|
||||
|
||||
angular.module("taigaProjects").service("tgGithubImportService", GithubImportService)
|
|
@ -0,0 +1,129 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: github-import.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "tgGithubImportService", ->
|
||||
$provide = null
|
||||
service = null
|
||||
mocks = {}
|
||||
|
||||
_mockResources = ->
|
||||
mocks.resources = {
|
||||
githubImporter: {
|
||||
listProjects: sinon.stub(),
|
||||
listUsers: sinon.stub(),
|
||||
importProject: sinon.stub(),
|
||||
getAuthUrl: sinon.stub(),
|
||||
authorize: sinon.stub()
|
||||
}
|
||||
}
|
||||
|
||||
$provide.value("tgResources", mocks.resources)
|
||||
|
||||
_mockLocation = ->
|
||||
mocks.location = {
|
||||
search: sinon.stub()
|
||||
}
|
||||
|
||||
mocks.location.search.returns({
|
||||
token: 123
|
||||
})
|
||||
|
||||
$provide.value("$location", mocks.location)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockResources()
|
||||
_mockLocation()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_tgGithubImportService_) ->
|
||||
service = _tgGithubImportService_
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProjects"
|
||||
|
||||
_setup()
|
||||
|
||||
it "fetch projects", (done) ->
|
||||
service.setToken(123)
|
||||
|
||||
mocks.resources.githubImporter.listProjects.withArgs(123).promise().resolve('projects')
|
||||
|
||||
service.fetchProjects().then () ->
|
||||
service.projects = "projects"
|
||||
done()
|
||||
|
||||
it "fetch user", (done) ->
|
||||
service.setToken(123)
|
||||
|
||||
projectId = 3
|
||||
mocks.resources.githubImporter.listUsers.withArgs(123, projectId).promise().resolve('users')
|
||||
|
||||
service.fetchUsers(projectId).then () ->
|
||||
service.projectUsers = 'users'
|
||||
done()
|
||||
|
||||
it "import project", () ->
|
||||
service.setToken(123)
|
||||
projectId = 2
|
||||
|
||||
service.importProject(projectId, true, true ,true)
|
||||
|
||||
expect(mocks.resources.githubImporter.importProject).to.have.been.calledWith(123, projectId, true, true, true)
|
||||
|
||||
it "get auth url", (done) ->
|
||||
service.setToken(123)
|
||||
projectId = 3
|
||||
|
||||
response = {
|
||||
data: {
|
||||
url: "url123"
|
||||
}
|
||||
}
|
||||
|
||||
mocks.resources.githubImporter.getAuthUrl.promise().resolve(response)
|
||||
|
||||
service.getAuthUrl().then (url) ->
|
||||
expect(url).to.be.equal("url123")
|
||||
done()
|
||||
|
||||
it "authorize", (done) ->
|
||||
service.setToken(123)
|
||||
projectId = 3
|
||||
verifyCode = 12345
|
||||
|
||||
response = {
|
||||
data: {
|
||||
token: "token123"
|
||||
}
|
||||
}
|
||||
|
||||
mocks.resources.githubImporter.authorize.withArgs(verifyCode).promise().resolve(response)
|
||||
|
||||
service.authorize(verifyCode).then (token) ->
|
||||
expect(token).to.be.equal("token123")
|
||||
done()
|
|
@ -0,0 +1,13 @@
|
|||
.create-project-action
|
||||
button.trans-button.create-project-action-cancel(
|
||||
type="button"
|
||||
ng-click="vm.onCancelForm()"
|
||||
title="{{'PROJECT.CREATE.BACK' | translate}}"
|
||||
translate="PROJECT.CREATE.BACK"
|
||||
)
|
||||
button.button-green.create-project-action-submit.e2e-create-project-action-submit(
|
||||
type="submit"
|
||||
ng-disabled="projectForm.$invalid || vm.isDisabled()"
|
||||
tg-loading="vm.formSubmitLoading"
|
||||
translate="PROJECT.CREATE.TITLE"
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
fieldset
|
||||
textarea.e2e-create-project-description(
|
||||
ng-model="vm.projectForm.description"
|
||||
placeholder="{{'PROJECT.COMMON.PROJECT_DESCRIPTION' | translate}}"
|
||||
data-required="true"
|
||||
required
|
||||
)
|
|
@ -0,0 +1,20 @@
|
|||
fieldset.create-project-check
|
||||
label(for="links")
|
||||
span.title(
|
||||
translate="PROJECT.IMPORT.LINKS"
|
||||
translate-values="{platform: vm.platformName}"
|
||||
)
|
||||
span.description(
|
||||
translate="PROJECT.IMPORT.LINKS_DESCRIPTION"
|
||||
translate-values="{platform: vm.platformName}"
|
||||
)
|
||||
|
||||
.check
|
||||
input.activate-input(
|
||||
ng-model="vm.projectForm.keepExternalReference"
|
||||
name="links"
|
||||
type="checkbox"
|
||||
)
|
||||
div
|
||||
span.check-text.check-yes(translate="COMMON.YES")
|
||||
span.check-text.check-no(translate="COMMON.NO")
|
|
@ -0,0 +1,13 @@
|
|||
fieldset
|
||||
label(
|
||||
translate="PROJECT.COMMON.DETAILS"
|
||||
for="project-name"
|
||||
)
|
||||
input.e2e-create-project-title(
|
||||
type="text"
|
||||
ng-model="vm.projectForm.name"
|
||||
placeholder="{{'PROJECT.COMMON.PROJECT_TITLE' | translate}}"
|
||||
name="project-name"
|
||||
data-required="true"
|
||||
required
|
||||
)
|
|
@ -0,0 +1,30 @@
|
|||
.create-project-privacity(role="group")
|
||||
fieldset
|
||||
input(
|
||||
type="radio"
|
||||
name="is_private"
|
||||
id="template-public"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="false"
|
||||
ng-model="vm.projectForm.is_private"
|
||||
required
|
||||
ng-checked="vm.canCreatePublicProjects"
|
||||
)
|
||||
label(for="template-public")
|
||||
tg-svg(svg-icon="icon-discover")
|
||||
span(translate="PROJECT.CREATE.PUBLIC_PROJECT")
|
||||
fieldset
|
||||
input(
|
||||
type="radio"
|
||||
name="is_private"
|
||||
id="template-private"
|
||||
data-required="true"
|
||||
ng-value="true"
|
||||
ng-model="vm.projectForm.is_private"
|
||||
aria-hidden="true"
|
||||
required
|
||||
)
|
||||
label(for="template-private")
|
||||
tg-svg(svg-icon="icon-lock")
|
||||
span(translate="PROJECT.CREATE.PRIVATE_PROJECT")
|
|
@ -0,0 +1,150 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-project-members.controller.coffee
|
||||
###
|
||||
|
||||
class ImportProjectMembersController
|
||||
@.$inject = [
|
||||
'tgCurrentUserService',
|
||||
'tgUserService'
|
||||
]
|
||||
|
||||
constructor: (@currentUserService, @userService) ->
|
||||
@.selectImportUserLightbox = false
|
||||
@.warningImportUsers = false
|
||||
@.cancelledUsers = Immutable.List()
|
||||
@.selectedUsers = Immutable.List()
|
||||
@.selectableUsers = Immutable.List()
|
||||
@.userContacts = Immutable.List()
|
||||
|
||||
fetchUser: () ->
|
||||
@.currentUser = @currentUserService.getUser()
|
||||
|
||||
@userService.getContacts(@.currentUser.get('id')).then (userContacts) =>
|
||||
@.userContacts = userContacts
|
||||
@.refreshSelectableUsers()
|
||||
|
||||
searchUser: (user) ->
|
||||
@.selectImportUserLightbox = true
|
||||
@.searchingUser = user
|
||||
|
||||
beforeSubmitUsers: () ->
|
||||
if @.selectedUsers.size != @.members.size
|
||||
@.warningImportUsers = true
|
||||
else
|
||||
@.submit()
|
||||
|
||||
confirmUser: (externalUser, taigaUser) ->
|
||||
@.selectImportUserLightbox = false
|
||||
|
||||
user = Immutable.Map()
|
||||
user = user.set('user', externalUser)
|
||||
user = user.set('taigaUser', taigaUser)
|
||||
|
||||
@.selectedUsers = @.selectedUsers.push(user)
|
||||
@.discardSuggestedUser(externalUser)
|
||||
|
||||
@.refreshSelectableUsers()
|
||||
|
||||
unselectUser: (user) ->
|
||||
index = @.selectedUsers.findIndex (it) -> it.getIn(['user', 'id']) == user.get('id')
|
||||
|
||||
@.selectedUsers = @.selectedUsers.delete(index)
|
||||
@.refreshSelectableUsers()
|
||||
|
||||
discardSuggestedUser: (member) ->
|
||||
@.cancelledUsers = @.cancelledUsers.push(member.get('id'))
|
||||
|
||||
getSelectedMember: (member) ->
|
||||
return @.selectedUsers.find (it) ->
|
||||
return it.getIn(['user', 'id']) == member.get('id')
|
||||
|
||||
isMemberSelected: (member) ->
|
||||
return !!@.getSelectedMember(member)
|
||||
|
||||
getUser: (user) ->
|
||||
userSelected = @.getSelectedMember(user)
|
||||
|
||||
if userSelected
|
||||
return userSelected.get('taigaUser')
|
||||
else
|
||||
return null
|
||||
|
||||
submit: () ->
|
||||
@.warningImportUsers = false
|
||||
|
||||
users = Immutable.Map()
|
||||
|
||||
@.selectedUsers.map (it) ->
|
||||
id = ''
|
||||
|
||||
if _.isString(it.get('taigaUser'))
|
||||
id = it.get('taigaUser')
|
||||
else
|
||||
id = it.getIn(['taigaUser', 'id'])
|
||||
|
||||
users = users.set(it.getIn(['user', 'id']), id)
|
||||
|
||||
@.onSubmit({users: users})
|
||||
|
||||
checkUsersLimit: () ->
|
||||
@.limitMembersPrivateProject = @currentUserService.canAddMembersPrivateProject(@.members.size + 1)
|
||||
@.limitMembersPublicProject = @currentUserService.canAddMembersPublicProject(@.members.size + 1)
|
||||
|
||||
showSuggestedMatch: (member) ->
|
||||
return member.get('user') && @.cancelledUsers.indexOf(member.get('id')) == -1 && !@.isMemberSelected(member)
|
||||
|
||||
getDistinctSelectedTaigaUsers: () ->
|
||||
ids = []
|
||||
|
||||
users = @.selectedUsers.filter (it) ->
|
||||
id = it.getIn(['taigaUser', 'id'])
|
||||
|
||||
if ids.indexOf(id) == -1
|
||||
ids.push(id)
|
||||
return true
|
||||
|
||||
return false
|
||||
|
||||
return users.filter (it) =>
|
||||
return it.getIn(['taigaUser', 'id']) != @.currentUser.get('id')
|
||||
|
||||
refreshSelectableUsers: () ->
|
||||
@.importMoreUsersDisabled = @.isImportMoreUsersDisabled()
|
||||
|
||||
if @.importMoreUsersDisabled
|
||||
users = @.getDistinctSelectedTaigaUsers()
|
||||
|
||||
@.selectableUsers = users.map (it) -> return it.get('taigaUser')
|
||||
else
|
||||
@.selectableUsers = @.userContacts
|
||||
|
||||
@.selectableUsers = @.selectableUsers.push(@.currentUser)
|
||||
|
||||
isImportMoreUsersDisabled: () ->
|
||||
users = @.getDistinctSelectedTaigaUsers()
|
||||
|
||||
# currentUser + newUser = +2
|
||||
total = users.size + 2
|
||||
|
||||
|
||||
if @.project.get('is_private')
|
||||
return !@currentUserService.canAddMembersPrivateProject(total).valid
|
||||
else
|
||||
return !@currentUserService.canAddMembersPublicProject(total).valid
|
||||
|
||||
angular.module('taigaProjects').controller('ImportProjectMembersCtrl', ImportProjectMembersController)
|
|
@ -0,0 +1,367 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "ImportProjectMembersCtrl", ->
|
||||
$provide = null
|
||||
$controller = null
|
||||
mocks = {}
|
||||
|
||||
_mockCurrentUserService = ->
|
||||
mocks.currentUserService = {
|
||||
getUser: sinon.stub().returns(Immutable.fromJS({
|
||||
id: 1
|
||||
})),
|
||||
canAddMembersPrivateProject: sinon.stub(),
|
||||
canAddMembersPublicProject: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgCurrentUserService", mocks.currentUserService)
|
||||
|
||||
_mockUserService = ->
|
||||
mocks.userService = {
|
||||
getContacts: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgUserService", mocks.userService)
|
||||
|
||||
_inject = ->
|
||||
inject (_$controller_) ->
|
||||
$controller = _$controller_
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockCurrentUserService()
|
||||
_mockUserService()
|
||||
|
||||
return null
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProjects"
|
||||
|
||||
_setup()
|
||||
|
||||
it "fetch user info", (done) ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.refreshSelectableUsers = sinon.spy()
|
||||
|
||||
mocks.userService.getContacts.withArgs(1).promise().resolve('contacts')
|
||||
|
||||
ctrl.fetchUser().then () ->
|
||||
expect(ctrl.userContacts).to.be.equal('contacts')
|
||||
expect(ctrl.refreshSelectableUsers).have.been.called
|
||||
done()
|
||||
|
||||
it "search user", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
user = {
|
||||
id: 1,
|
||||
name: "username"
|
||||
}
|
||||
|
||||
ctrl.searchUser(user)
|
||||
|
||||
expect(ctrl.selectImportUserLightbox).to.be.true
|
||||
expect(ctrl.searchingUser).to.be.equal(user)
|
||||
|
||||
it "prepare submit users, warning if needed", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
user = {
|
||||
id: 1,
|
||||
name: "username"
|
||||
}
|
||||
|
||||
ctrl.selectedUsers = Immutable.fromJS([
|
||||
{id: 1},
|
||||
{id: 2}
|
||||
])
|
||||
|
||||
ctrl.members = Immutable.fromJS([
|
||||
{id: 1}
|
||||
])
|
||||
|
||||
ctrl.beforeSubmitUsers()
|
||||
|
||||
expect(ctrl.warningImportUsers).to.be.true
|
||||
|
||||
it "prepare submit users, submit", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
user = {
|
||||
id: 1,
|
||||
name: "username"
|
||||
}
|
||||
|
||||
ctrl.selectedUsers = Immutable.fromJS([
|
||||
{id: 1}
|
||||
])
|
||||
|
||||
ctrl.members = Immutable.fromJS([
|
||||
{id: 1}
|
||||
])
|
||||
|
||||
|
||||
ctrl.submit = sinon.spy()
|
||||
ctrl.beforeSubmitUsers()
|
||||
|
||||
expect(ctrl.warningImportUsers).to.be.false
|
||||
expect(ctrl.submit).have.been.called
|
||||
|
||||
it "confirm user", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.discardSuggestedUser = sinon.spy()
|
||||
ctrl.refreshSelectableUsers = sinon.spy()
|
||||
|
||||
ctrl.confirmUser('user', 'taiga-user')
|
||||
|
||||
expect(ctrl.selectedUsers.size).to.be.equal(1)
|
||||
|
||||
expect(ctrl.selectedUsers.get(0).get('user')).to.be.equal('user')
|
||||
expect(ctrl.selectedUsers.get(0).get('taigaUser')).to.be.equal('taiga-user')
|
||||
expect(ctrl.discardSuggestedUser).have.been.called
|
||||
expect(ctrl.refreshSelectableUsers).have.been.called
|
||||
|
||||
it "discard suggested user", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.discardSuggestedUser(Immutable.fromJS({
|
||||
id: 3
|
||||
}))
|
||||
|
||||
expect(ctrl.cancelledUsers.get(0)).to.be.equal(3)
|
||||
|
||||
it "clean member selection", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.refreshSelectableUsers = sinon.spy()
|
||||
|
||||
ctrl.selectedUsers = Immutable.fromJS([
|
||||
{
|
||||
user: {
|
||||
id: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
user: {
|
||||
id: 2
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
ctrl.unselectUser(Immutable.fromJS({
|
||||
id: 2
|
||||
}))
|
||||
|
||||
expect(ctrl.selectedUsers.size).to.be.equal(1)
|
||||
expect(ctrl.refreshSelectableUsers).have.been.called
|
||||
|
||||
|
||||
it "get a selected member", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
member = Immutable.fromJS({
|
||||
id: 3
|
||||
})
|
||||
|
||||
ctrl.selectedUsers = ctrl.selectedUsers.push(Immutable.fromJS({
|
||||
user: {
|
||||
id: 3
|
||||
}
|
||||
}))
|
||||
|
||||
user = ctrl.getSelectedMember(member)
|
||||
|
||||
expect(user.getIn(['user', 'id'])).to.be.equal(3)
|
||||
|
||||
it "submit", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.selectedUsers = ctrl.selectedUsers.push(Immutable.fromJS({
|
||||
user: {
|
||||
id: 3
|
||||
},
|
||||
taigaUser: {
|
||||
id: 2
|
||||
}
|
||||
}))
|
||||
|
||||
ctrl.selectedUsers = ctrl.selectedUsers.push(Immutable.fromJS({
|
||||
user: {
|
||||
id: 3
|
||||
},
|
||||
taigaUser: "xx@yy.com"
|
||||
}))
|
||||
|
||||
|
||||
ctrl.onSubmit = sinon.stub()
|
||||
|
||||
ctrl.submit()
|
||||
|
||||
user = Immutable.Map()
|
||||
user = user.set(3, 2)
|
||||
|
||||
expect(ctrl.onSubmit).have.been.called
|
||||
expect(ctrl.warningImportUsers).to.be.false
|
||||
|
||||
it "show suggested match", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.isMemberSelected = sinon.stub().returns(false)
|
||||
ctrl.cancelledUsers = [
|
||||
3
|
||||
]
|
||||
|
||||
member = Immutable.fromJS({
|
||||
id: 1,
|
||||
user: {
|
||||
id: 10
|
||||
}
|
||||
})
|
||||
|
||||
expect(ctrl.showSuggestedMatch(member)).to.be.true
|
||||
|
||||
it "doesn't show suggested match", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.isMemberSelected = sinon.stub().returns(false)
|
||||
ctrl.cancelledUsers = [
|
||||
3
|
||||
]
|
||||
|
||||
member = Immutable.fromJS({
|
||||
id: 3,
|
||||
user: {
|
||||
id: 10
|
||||
}
|
||||
})
|
||||
|
||||
expect(ctrl.showSuggestedMatch(member)).to.be.false
|
||||
|
||||
it "check users limit", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.members = Immutable.fromJS([
|
||||
1, 2, 3
|
||||
])
|
||||
|
||||
mocks.currentUserService.canAddMembersPrivateProject.withArgs(4).returns('xx')
|
||||
mocks.currentUserService.canAddMembersPublicProject.withArgs(4).returns('yy')
|
||||
|
||||
ctrl.checkUsersLimit()
|
||||
|
||||
expect(ctrl.limitMembersPrivateProject).to.be.equal('xx')
|
||||
expect(ctrl.limitMembersPublicProject).to.be.equal('yy')
|
||||
|
||||
|
||||
it "get distict select taiga users excluding the current user", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
ctrl.selectedUsers = Immutable.fromJS([
|
||||
{
|
||||
taigaUser: {
|
||||
id: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
taigaUser: {
|
||||
id: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
taigaUser: {
|
||||
id: 3
|
||||
}
|
||||
},
|
||||
{
|
||||
taigaUser: {
|
||||
id: 5
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
ctrl.currentUser = Immutable.fromJS({
|
||||
id: 5
|
||||
})
|
||||
|
||||
users = ctrl.getDistinctSelectedTaigaUsers()
|
||||
|
||||
expect(users.size).to.be.equal(2)
|
||||
|
||||
it "refresh selectable users array with all users available", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.isImportMoreUsersDisabled = sinon.stub().returns(false)
|
||||
|
||||
ctrl.userContacts = Immutable.fromJS([1])
|
||||
ctrl.currentUser = 2
|
||||
|
||||
ctrl.refreshSelectableUsers()
|
||||
|
||||
expect(ctrl.selectableUsers.toJS()).to.be.eql([1, 2])
|
||||
|
||||
|
||||
it "refresh selectable users array with the selected ones", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.getDistinctSelectedTaigaUsers = sinon.stub().returns(Immutable.fromJS([
|
||||
{taigaUser: 1}
|
||||
]))
|
||||
|
||||
ctrl.isImportMoreUsersDisabled = sinon.stub().returns(true)
|
||||
|
||||
ctrl.userContacts = Immutable.fromJS([1])
|
||||
ctrl.currentUser = 2
|
||||
|
||||
ctrl.refreshSelectableUsers()
|
||||
|
||||
expect(ctrl.selectableUsers.toJS()).to.be.eql([1, 2])
|
||||
|
||||
it "import more user disable in private project", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.project = Immutable.fromJS({
|
||||
is_private: true
|
||||
})
|
||||
|
||||
ctrl.getDistinctSelectedTaigaUsers = sinon.stub().returns(Immutable.fromJS([1,2,3]))
|
||||
|
||||
mocks.currentUserService.canAddMembersPrivateProject.withArgs(5).returns({valid: true})
|
||||
|
||||
expect(ctrl.isImportMoreUsersDisabled()).to.be.false
|
||||
|
||||
it "import more user disable in public project", () ->
|
||||
ctrl = $controller("ImportProjectMembersCtrl")
|
||||
|
||||
ctrl.project = Immutable.fromJS({
|
||||
is_private: false
|
||||
})
|
||||
|
||||
ctrl.getDistinctSelectedTaigaUsers = sinon.stub().returns(Immutable.fromJS([1,2,3]))
|
||||
|
||||
mocks.currentUserService.canAddMembersPublicProject.withArgs(5).returns({valid: true})
|
||||
|
||||
expect(ctrl.isImportMoreUsersDisabled()).to.be.false
|
|
@ -0,0 +1,43 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-project-form.directive.coffee
|
||||
###
|
||||
|
||||
ImportProjectMembersDirective = () ->
|
||||
return {
|
||||
link: (scope, elm, attr, ctrl) ->
|
||||
ctrl.fetchUser()
|
||||
|
||||
scope.$watch('vm.members', ctrl.checkUsersLimit.bind(ctrl))
|
||||
|
||||
templateUrl:"projects/create/import-project-members/import-project-members.html",
|
||||
controller: "ImportProjectMembersCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {
|
||||
members: '<',
|
||||
project: '<',
|
||||
onSubmit: '&',
|
||||
platform: '@',
|
||||
logo: '@',
|
||||
onCancel: '&'
|
||||
}
|
||||
}
|
||||
|
||||
ImportProjectMembersDirective.$inject = []
|
||||
|
||||
angular.module("taigaProjects").directive("tgImportProjectMembers", ImportProjectMembersDirective)
|
|
@ -0,0 +1,85 @@
|
|||
.import-project-members
|
||||
div(ng-include="'projects/create/import/import-header.html'")
|
||||
|
||||
h2.import-project-members-title(translate="PROJECT.IMPORT.PROJECT_MEMBERS")
|
||||
p(
|
||||
translate="PROJECT.IMPORT.PROCESS_DESCRIPTION",
|
||||
translate-values="{'platform': vm.platform}"
|
||||
)
|
||||
|
||||
tg-create-project-members-restrictions(
|
||||
is-private="vm.project.get('is_private')"
|
||||
limit-members-private-project="vm.limitMembersPrivateProject"
|
||||
limit-members-public-project="vm.limitMembersPublicProject"
|
||||
)
|
||||
|
||||
.import-project-members-system(ng-if="vm.members.size")
|
||||
.import-project-members-logo
|
||||
img(ng-src="{{vm.logo}}")
|
||||
.import-project-members-logo
|
||||
img(
|
||||
src="/#{v}/images/logo-color.png"
|
||||
alt="Taiga Logo"
|
||||
)
|
||||
|
||||
ul(ng-if="vm.members.size")
|
||||
li.import-project-members-row(tg-repeat="member in vm.members track by member.get('id')")
|
||||
.import-project-members-single
|
||||
.avatar.empty(ng-if="!member.get('avatar')") {{member.get('full_name')[0].toUpperCase()}}
|
||||
.avatar(ng-if="member.get('avatar')")
|
||||
img(ng-src="{{member.get('avatar')}}")
|
||||
span.import-project-members-username {{member.get('full_name') || member.get('username') }}
|
||||
|
||||
.import-project-members-actions
|
||||
.import-project-members-match(ng-if="vm.showSuggestedMatch(member)")
|
||||
span(
|
||||
translate="PROJECT.IMPORT.MATCH"
|
||||
translate-values="{user_external:member.get('full_name'), user_internal: member.getIn(['user', 'full_name'])}"
|
||||
)
|
||||
button.import-project-members-match-true(ng-click="vm.confirmUser(member, member.get('user'))")
|
||||
tg-svg(svg-icon="icon-check-empty")
|
||||
button.import-project-members-match-false(ng-click="vm.discardSuggestedUser(member)")
|
||||
tg-svg(svg-icon="icon-close")
|
||||
|
||||
.import-project-members-selected(ng-if="vm.getUser(member) && !vm.showSuggestedMatch(member)")
|
||||
button.import-project-members-delete(ng-click="vm.unselectUser(member)")
|
||||
tg-svg(svg-icon="icon-close")
|
||||
span {{vm.getUser(member).get('full_name') || vm.getUser(member)}}
|
||||
span.import-project-members-selected-img
|
||||
img(tg-avatar="vm.getUser(member)")
|
||||
|
||||
button.button.button-trans.import-project-members-choose.ng-animate-disabled(
|
||||
ng-if="!vm.getUser(member) && !vm.showSuggestedMatch(member)"
|
||||
ng-click="vm.searchUser(member)"
|
||||
translate="PROJECT.IMPORT.CHOOSE"
|
||||
)
|
||||
|
||||
.create-project-action
|
||||
button.trans-button.create-project-action-cancel(
|
||||
type="button"
|
||||
ng-click="vm.onCancel()"
|
||||
title="{{'PROJECT.CREATE.BACK' | translate}}"
|
||||
translate="PROJECT.CREATE.BACK"
|
||||
)
|
||||
button.button.button-green.create-project-action-submit(
|
||||
ng-if="vm.members.size > 0"
|
||||
ng-click="vm.beforeSubmitUsers()"
|
||||
translate="PROJECT.IMPORT.IMPORT"
|
||||
)
|
||||
|
||||
tg-select-import-user-lightbox.lightbox(
|
||||
is-private="vm.project.get('is_private')"
|
||||
limit-members-private-project="vm.limitMembersPrivateProject"
|
||||
limit-members-public-project="vm.limitMembersPublicProject"
|
||||
visible="vm.selectImportUserLightbox"
|
||||
user="vm.searchingUser"
|
||||
selectable-users="vm.selectableUsers"
|
||||
on-close="vm.selectImportUserLightbox = false"
|
||||
on-select-user="vm.confirmUser(user, taigaUser)"
|
||||
)
|
||||
|
||||
tg-warning-user-import-lightbox.lightbox(
|
||||
visible="vm.warningImportUsers"
|
||||
on-confirm="vm.submit()"
|
||||
on-close="vm.warningImportUsers = false"
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
.import-project-members {
|
||||
@include import-members;
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-project-selector.controller.coffee
|
||||
###
|
||||
|
||||
class ImportProjectSelectorController
|
||||
selectProject: (project) ->
|
||||
@.onSelectProject({project: Immutable.fromJS(project)})
|
||||
|
||||
angular.module('taigaProjects').controller('ImportProjectSelectorCtrl', ImportProjectSelectorController)
|
|
@ -0,0 +1,36 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-project-selector.directive.coffee
|
||||
###
|
||||
|
||||
ImportProjectSelectorDirective = () ->
|
||||
return {
|
||||
templateUrl:"projects/create/import-project-selector/import-project-selector.html",
|
||||
controller: "ImportProjectSelectorCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {
|
||||
projects: '<',
|
||||
onCancel: '&',
|
||||
onSelectProject: '&',
|
||||
platfrom: '@',
|
||||
logo: '@',
|
||||
search: '@'
|
||||
}
|
||||
}
|
||||
|
||||
angular.module("taigaProjects").directive("tgImportProjectSelector", ImportProjectSelectorDirective)
|
|
@ -0,0 +1,34 @@
|
|||
.import-project-selector
|
||||
div(ng-include="'projects/create/import/import-header.html'")
|
||||
|
||||
.import-project-selector
|
||||
|
||||
.import-project-selector-service
|
||||
img(
|
||||
ng-src="{{vm.logo}}"
|
||||
)
|
||||
|
||||
.import-project-selector-boards
|
||||
form.import-project-selector-filter(ng-if="vm.projects.size > 5")
|
||||
input(
|
||||
ng-model="vm.searchText"
|
||||
placeholder="{{ vm.search }}"
|
||||
)
|
||||
tg-svg(
|
||||
svg-icon="icon-search"
|
||||
title="{{'SEARCH.TITLE_ACTION_SEARCH' | translate}}"
|
||||
)
|
||||
|
||||
ul.import-project-board-list
|
||||
li.import-project-selector-title(
|
||||
ng-repeat="project in vm.projects | toMutable | orderBy:'name' | filter:vm.searchText track by project.id"
|
||||
ng-click="vm.selectProject(project)"
|
||||
) {{project.name}}
|
||||
|
||||
.create-project-action
|
||||
button.trans-button.create-project-action-cancel(
|
||||
type="button"
|
||||
ng-click="vm.onCancel()"
|
||||
title="{{'COMMON.CANCEL' | translate}}"
|
||||
translate="COMMON.CANCEL"
|
||||
)
|
|
@ -0,0 +1,3 @@
|
|||
.import-project-selector {
|
||||
@include import-project-selector;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-project.controller.coffee
|
||||
###
|
||||
|
||||
class ImportTaigaController
|
||||
@.$inject = [
|
||||
'$tgConfirm',
|
||||
'$tgResources',
|
||||
'tgImportProjectService',
|
||||
'$translate'
|
||||
]
|
||||
|
||||
constructor: (@confirm, @rs, @importProjectService, @translate) ->
|
||||
|
||||
importTaiga: (files) ->
|
||||
file = files[0]
|
||||
|
||||
loader = @confirm.loader(@translate.instant('PROJECT.IMPORT.IN_PROGRESS.TITLE'), @translate.instant('PROJECT.IMPORT.IN_PROGRESS.DESCRIPTION'), true)
|
||||
|
||||
loader.start()
|
||||
|
||||
promise = @rs.projects.import(file, loader.update)
|
||||
|
||||
@importProjectService.importPromise(promise).finally () => loader.stop()
|
||||
|
||||
return
|
||||
|
||||
angular.module("taigaProjects").controller("ImportTaigaCtrl", ImportTaigaController)
|
|
@ -0,0 +1,29 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-taiga.directive.coffee
|
||||
###
|
||||
|
||||
ImportTaigaDirective = () ->
|
||||
return {
|
||||
templateUrl:"projects/create/import-taiga/import-taiga.html",
|
||||
controller: "ImportTaigaCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {}
|
||||
}
|
||||
|
||||
angular.module("taigaProjects").directive("tgImportTaiga", ImportTaigaDirective)
|
|
@ -0,0 +1,15 @@
|
|||
input.hidden(
|
||||
tg-file-change="vm.importTaiga(files)"
|
||||
type="file"
|
||||
)
|
||||
.import-project-logo
|
||||
img(
|
||||
src="/#{v}/images/logo-color.png"
|
||||
alt="Taiga Logo"
|
||||
)
|
||||
.import-project-name-wrapper
|
||||
p.import-project-name(
|
||||
href="#"
|
||||
title="Taiga"
|
||||
) Taiga
|
||||
p.import-project-description(translate="PROJECT.IMPORT.TAIGA.SELECTOR")
|
|
@ -0,0 +1,2 @@
|
|||
section.import-project-from
|
||||
h1.create-project-title(translate="PROJECT.IMPORT.TITLE")
|
|
@ -0,0 +1,16 @@
|
|||
LbImportErrorDirective = (lightboxService) ->
|
||||
link = (scope, el, attrs) ->
|
||||
lightboxService.open(el)
|
||||
|
||||
scope.close = () ->
|
||||
lightboxService.close(el)
|
||||
return
|
||||
|
||||
return {
|
||||
templateUrl: "projects/create/import/import-project-error-lb.html",
|
||||
link: link
|
||||
}
|
||||
|
||||
LbImportErrorDirective.$inject = ["lightboxService"]
|
||||
|
||||
angular.module("taigaProjects").directive("tgLbImportError", LbImportErrorDirective)
|
|
@ -0,0 +1,113 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-project.controller.coffee
|
||||
###
|
||||
|
||||
class ImportProjectController
|
||||
@.$inject = [
|
||||
'tgTrelloImportService',
|
||||
'tgJiraImportService',
|
||||
'tgGithubImportService',
|
||||
'tgAsanaImportService',
|
||||
'$location',
|
||||
'$window',
|
||||
'$routeParams',
|
||||
'$tgNavUrls',
|
||||
'$tgConfig',
|
||||
]
|
||||
|
||||
constructor: (@trelloService, @jiraService, @githubService, @asanaService, @location, @window, @routeParams, @tgNavUrls, @config) ->
|
||||
|
||||
start: ->
|
||||
@.token = null
|
||||
@.from = @routeParams.platform
|
||||
|
||||
locationSearch = @location.search()
|
||||
|
||||
if @.from == "asana"
|
||||
asanaOauthToken = locationSearch.code
|
||||
if locationSearch.code
|
||||
asanaOauthToken = locationSearch.code
|
||||
|
||||
return @asanaService.authorize(asanaOauthToken).then ((token) =>
|
||||
@location.search({token: encodeURIComponent(JSON.stringify(token))})
|
||||
), @.cancelCurrentImport.bind(this)
|
||||
else
|
||||
@.token = JSON.parse(decodeURIComponent(locationSearch.token))
|
||||
@asanaService.setToken(@.token)
|
||||
|
||||
if @.from == 'trello'
|
||||
if locationSearch.oauth_verifier
|
||||
trelloOauthToken = locationSearch.oauth_verifier
|
||||
return @trelloService.authorize(trelloOauthToken).then ((token) =>
|
||||
@location.search({token: token})
|
||||
), @.cancelCurrentImport.bind(this)
|
||||
else if locationSearch.token
|
||||
@.token = locationSearch.token
|
||||
@trelloService.setToken(locationSearch.token)
|
||||
|
||||
if @.from == "github"
|
||||
if locationSearch.code
|
||||
githubOauthToken = locationSearch.code
|
||||
|
||||
return @githubService.authorize(githubOauthToken).then ((token) =>
|
||||
@location.search({token: token})
|
||||
), @.cancelCurrentImport.bind(this)
|
||||
else if locationSearch.token
|
||||
@.token = locationSearch.token
|
||||
@githubService.setToken(locationSearch.token)
|
||||
|
||||
if @.from == "jira"
|
||||
jiraOauthToken = locationSearch.oauth_token
|
||||
|
||||
if jiraOauthToken
|
||||
return @jiraService.authorize().then ((data) =>
|
||||
@location.search({token: data.token, url: data.url})
|
||||
), @.cancelCurrentImport.bind(this)
|
||||
else
|
||||
@.token = locationSearch.token
|
||||
@jiraService.setToken(locationSearch.token, locationSearch.url)
|
||||
|
||||
select: (from) ->
|
||||
if from == "trello"
|
||||
@trelloService.getAuthUrl().then (url) =>
|
||||
@window.open(url, "_self")
|
||||
else if from == "jira"
|
||||
@jiraService.getAuthUrl(@.jiraUrl).then (url) =>
|
||||
@window.open(url, "_self")
|
||||
else if from == "github"
|
||||
callbackUri = @location.absUrl() + "/github"
|
||||
@githubService.getAuthUrl(callbackUri).then (url) =>
|
||||
@window.open(url, "_self")
|
||||
else if from == "asana"
|
||||
@asanaService.getAuthUrl().then (url) =>
|
||||
@window.open(url, "_self")
|
||||
else
|
||||
@.from = from
|
||||
|
||||
unfoldOptions: (options) ->
|
||||
@.unfoldedOptions = options
|
||||
|
||||
isActiveImporter: (importer) ->
|
||||
if @config.get('importers').indexOf(importer) == -1
|
||||
return false
|
||||
return true
|
||||
|
||||
cancelCurrentImport: () ->
|
||||
@location.url(@tgNavUrls.resolve('create-project-import'))
|
||||
|
||||
angular.module("taigaProjects").controller("ImportProjectCtrl", ImportProjectController)
|
|
@ -0,0 +1,183 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-project.controller.spec.coffee
|
||||
###
|
||||
|
||||
describe "ImportProjectCtrl", ->
|
||||
$provide = null
|
||||
$controller = null
|
||||
mocks = {}
|
||||
|
||||
_mockConfig = ->
|
||||
mocks.config = Immutable.fromJS({
|
||||
importers: ['trello', 'github', 'jira', 'asana']
|
||||
})
|
||||
|
||||
$provide.value("$tgConfig", mocks.config)
|
||||
|
||||
_mockTrelloImportService = ->
|
||||
mocks.trelloService = {
|
||||
authorize: sinon.stub(),
|
||||
getAuthUrl: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgTrelloImportService", mocks.trelloService)
|
||||
|
||||
_mockJiraImportService = ->
|
||||
mocks.jiraService = {
|
||||
authorize: sinon.stub(),
|
||||
getAuthUrl: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgJiraImportService", mocks.jiraService)
|
||||
|
||||
_mockGithubImportService = ->
|
||||
mocks.githubService = {
|
||||
authorize: sinon.stub(),
|
||||
getAuthUrl: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgGithubImportService", mocks.githubService)
|
||||
|
||||
_mockAsanaImportService = ->
|
||||
mocks.asanaService = {
|
||||
authorize: sinon.stub(),
|
||||
getAuthUrl: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgAsanaImportService", mocks.asanaService)
|
||||
|
||||
_mockWindow = ->
|
||||
mocks.window = {
|
||||
open: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$window", mocks.window)
|
||||
|
||||
_mockLocation = ->
|
||||
mocks.location = {
|
||||
search: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$location", mocks.location)
|
||||
|
||||
_mockRouteParams = ->
|
||||
mocks.routeParams = {
|
||||
platform: null
|
||||
}
|
||||
|
||||
$provide.value("$routeParams", mocks.routeParams)
|
||||
|
||||
_mockTgNavUrls = ->
|
||||
mocks.tgNavUrls = {
|
||||
resolve: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$tgNavUrls", mocks.tgNavUrls)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockGithubImportService()
|
||||
_mockTrelloImportService()
|
||||
_mockJiraImportService()
|
||||
_mockAsanaImportService()
|
||||
_mockWindow()
|
||||
_mockLocation()
|
||||
_mockTgNavUrls()
|
||||
_mockRouteParams()
|
||||
_mockConfig()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_$controller_) ->
|
||||
$controller = _$controller_
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProjects"
|
||||
|
||||
_setup()
|
||||
|
||||
it "initialize form with trello", (done) ->
|
||||
searchResult = {
|
||||
oauth_verifier: 123,
|
||||
token: "token"
|
||||
}
|
||||
|
||||
mocks.location.search.returns(searchResult)
|
||||
mocks.trelloService.authorize.withArgs(123).promise().resolve("token2")
|
||||
|
||||
ctrl = $controller("ImportProjectCtrl")
|
||||
|
||||
mocks.routeParams.platform = 'trello'
|
||||
|
||||
ctrl.start().then () ->
|
||||
expect(mocks.location.search).have.been.calledWith({token: "token2"})
|
||||
|
||||
done()
|
||||
|
||||
it "initialize form with github", (done) ->
|
||||
searchResult = {
|
||||
code: 123,
|
||||
token: "token",
|
||||
from: "github"
|
||||
}
|
||||
|
||||
mocks.location.search.returns(searchResult)
|
||||
mocks.githubService.authorize.withArgs(123).promise().resolve("token2")
|
||||
|
||||
ctrl = $controller("ImportProjectCtrl")
|
||||
|
||||
mocks.routeParams.platform = 'github'
|
||||
|
||||
ctrl.start().then () ->
|
||||
expect(mocks.location.search).have.been.calledWith({token: "token2"})
|
||||
|
||||
done()
|
||||
|
||||
it "initialize form with asana", (done) ->
|
||||
searchResult = {
|
||||
code: 123,
|
||||
token: encodeURIComponent("{\"token\": 222}")
|
||||
from: "asana"
|
||||
}
|
||||
|
||||
mocks.location.search.returns(searchResult)
|
||||
mocks.asanaService.authorize.withArgs(123).promise().resolve("token2")
|
||||
|
||||
ctrl = $controller("ImportProjectCtrl")
|
||||
|
||||
mocks.routeParams.platform = 'asana'
|
||||
|
||||
ctrl.start().then () ->
|
||||
expect(mocks.location.search).have.been.calledWith({token: encodeURIComponent(JSON.stringify("token2"))})
|
||||
|
||||
done()
|
||||
|
||||
it "select trello import", () ->
|
||||
ctrl = $controller("ImportProjectCtrl")
|
||||
|
||||
mocks.trelloService.getAuthUrl.promise().resolve("url")
|
||||
|
||||
ctrl.select("trello").then () ->
|
||||
expect(mocks.window.open).have.been.calledWith("url", "_self")
|
|
@ -0,0 +1,38 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-project.directive.coffee
|
||||
###
|
||||
|
||||
ImportProjectDirective = () ->
|
||||
|
||||
link = (scope, el, attr, ctrl) ->
|
||||
ctrl.start()
|
||||
|
||||
return {
|
||||
link: link,
|
||||
templateUrl:"projects/create/import/import-project.html",
|
||||
controller: "ImportProjectCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {
|
||||
onCancelImport: '&'
|
||||
}
|
||||
}
|
||||
|
||||
ImportProjectDirective.$inject = []
|
||||
|
||||
angular.module("taigaProjects").directive("tgImportProject", ImportProjectDirective)
|
|
@ -0,0 +1,95 @@
|
|||
.create-project.import-project(ng-if="!vm.from")
|
||||
div(ng-include="'projects/create/import/import-header.html'")
|
||||
|
||||
ul.import-project-from
|
||||
li.import-project-from-site(
|
||||
tg-click-input-file,
|
||||
tg-import-taiga
|
||||
)
|
||||
li.import-project-from-site(ng-click="vm.unfoldOptions('jira')", ng-if="vm.isActiveImporter('jira')")
|
||||
.import-project-logo
|
||||
img(
|
||||
src="/#{v}/images/import-logos/jira.png"
|
||||
alt="Jira Logo"
|
||||
)
|
||||
.import-project-name-wrapper
|
||||
a.import-project-name(
|
||||
href="#"
|
||||
title="Jira"
|
||||
) Jira
|
||||
p.import-project-description(translate="PROJECT.IMPORT.JIRA.SELECTOR")
|
||||
fieldset.import-project-url(ng-if="vm.unfoldedOptions == 'jira'")
|
||||
label(
|
||||
for="jira-host"
|
||||
translate="PROJECT.IMPORT.JIRA.URL"
|
||||
)
|
||||
input.import-project-input(
|
||||
ng-keyup="$event.keyCode == 13 && vm.select('jira')"
|
||||
id="jira-host"
|
||||
ng-model="vm.jiraUrl"
|
||||
)
|
||||
button.button-green.import-project-button(
|
||||
ng-click="vm.select('jira')"
|
||||
title="{{'PROJECT.IMPORT.ACCEEDE' | translate}}"
|
||||
translate="PROJECT.IMPORT.ACCEEDE"
|
||||
)
|
||||
li.import-project-from-site(ng-click="vm.select('github')", ng-if="vm.isActiveImporter('github')")
|
||||
.import-project-logo
|
||||
img(
|
||||
src="/#{v}/images/import-logos/github.png"
|
||||
alt="Github Logo"
|
||||
)
|
||||
.import-project-name-wrapper
|
||||
a.import-project-name(
|
||||
href="#"
|
||||
title="Github"
|
||||
) Github
|
||||
p.import-project-description(translate="PROJECT.IMPORT.GITHUB.SELECTOR")
|
||||
li.import-project-from-site(ng-click="vm.select('trello')", ng-if="vm.isActiveImporter('trello')")
|
||||
.import-project-logo
|
||||
img(
|
||||
src="/#{v}/images/import-logos/trello.png"
|
||||
alt="Trello Logo"
|
||||
)
|
||||
.import-project-name-wrapper
|
||||
a.import-project-name(
|
||||
href="#"
|
||||
title="Trello"
|
||||
) Trello
|
||||
p.import-project-description(translate="PROJECT.IMPORT.TRELLO.SELECTOR")
|
||||
li.import-project-from-site(ng-click="vm.select('asana')", ng-if="vm.isActiveImporter('asana')")
|
||||
.import-project-logo
|
||||
img(
|
||||
src="/#{v}/images/import-logos/asana.png"
|
||||
alt="Asana Logo"
|
||||
)
|
||||
.import-project-name-wrapper
|
||||
a.import-project-name(
|
||||
href="#"
|
||||
title="Asana"
|
||||
) Asana
|
||||
p.import-project-description(translate="PROJECT.IMPORT.ASANA.SELECTOR")
|
||||
|
||||
.create-project-action
|
||||
a.trans-button.create-project-action-back(
|
||||
tg-nav="create-project",
|
||||
title="{{'PROJECT.CREATE.BACK' | translate}}"
|
||||
translate="PROJECT.CREATE.BACK"
|
||||
)
|
||||
|
||||
tg-trello-import(
|
||||
ng-if="vm.from == 'trello' && vm.token"
|
||||
on-cancel="vm.cancelCurrentImport()"
|
||||
)
|
||||
tg-jira-import(
|
||||
ng-if="vm.from == 'jira'"
|
||||
on-cancel="vm.cancelCurrentImport()"
|
||||
)
|
||||
tg-github-import(
|
||||
ng-if="vm.from == 'github' && vm.token"
|
||||
on-cancel="vm.cancelCurrentImport()"
|
||||
)
|
||||
tg-asana-import(
|
||||
ng-if="vm.from == 'asana' && vm.token"
|
||||
on-cancel="vm.cancelCurrentImport()"
|
||||
)
|
|
@ -0,0 +1,55 @@
|
|||
.import-project {
|
||||
&-from-site {
|
||||
align-items: center;
|
||||
border-bottom: 1px solid $whitish;
|
||||
color: $grayer;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
background: rgba($primary, .1);
|
||||
transition: background .3s ease-in;
|
||||
}
|
||||
&:first-child {
|
||||
border-top: 1px solid $whitish;
|
||||
.import-project-name {
|
||||
margin: 0;
|
||||
}
|
||||
.import-project-logo img {
|
||||
padding: 0 .9rem 0 1rem;
|
||||
width: 5.1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
&-logo {
|
||||
align-self: flex-start;
|
||||
margin-right: .5rem;
|
||||
img {
|
||||
padding: 0 1rem;
|
||||
width: 5rem;
|
||||
}
|
||||
}
|
||||
&-name-wrapper {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
&-description {
|
||||
@include font-type(light);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
&-url {
|
||||
margin-top: .5rem;
|
||||
}
|
||||
&-input {
|
||||
vertical-align: middle;
|
||||
}
|
||||
&-button {
|
||||
background: $primary;
|
||||
color: $white;
|
||||
padding: .25rem 1rem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,121 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-project.service.coffee
|
||||
###
|
||||
|
||||
class ImportProjectService extends taiga.Service
|
||||
@.$inject = [
|
||||
'tgCurrentUserService',
|
||||
'$tgAuth',
|
||||
'tgLightboxFactory',
|
||||
'$translate',
|
||||
'$tgConfirm',
|
||||
'$location',
|
||||
'$tgNavUrls'
|
||||
]
|
||||
|
||||
constructor: (@currentUserService, @tgAuth, @lightboxFactory, @translate, @confirm, @location, @tgNavUrls) ->
|
||||
|
||||
importPromise: (promise) ->
|
||||
return promise.then(@.importSuccess.bind(this), @.importError.bind(this))
|
||||
|
||||
importSuccess: (result) ->
|
||||
return @currentUserService.loadProjects().then () =>
|
||||
if result.status == 202 # Async mode
|
||||
title = @translate.instant('PROJECT.IMPORT.ASYNC_IN_PROGRESS_TITLE')
|
||||
message = @translate.instant('PROJECT.IMPORT.ASYNC_IN_PROGRESS_MESSAGE')
|
||||
@confirm.success(title, message)
|
||||
else # result.status == 201 # Sync mode
|
||||
ctx = {project: result.data.slug}
|
||||
@location.path(@tgNavUrls.resolve('project-admin-project-profile-details', ctx))
|
||||
msg = @translate.instant('PROJECT.IMPORT.SYNC_SUCCESS')
|
||||
@confirm.notify('success', msg)
|
||||
|
||||
importError: (result) ->
|
||||
return @tgAuth.refresh().then () =>
|
||||
restrictionError = @.getRestrictionError(result)
|
||||
|
||||
if restrictionError
|
||||
@lightboxFactory.create('tg-lb-import-error', {
|
||||
class: 'lightbox lightbox-import-error'
|
||||
}, restrictionError)
|
||||
|
||||
else
|
||||
errorMsg = @translate.instant("PROJECT.IMPORT.ERROR")
|
||||
|
||||
if result.status == 429 # TOO MANY REQUESTS
|
||||
errorMsg = @translate.instant("PROJECT.IMPORT.ERROR_TOO_MANY_REQUEST")
|
||||
else if result.data?._error_message
|
||||
errorMsg = @translate.instant("PROJECT.IMPORT.ERROR_MESSAGE", {error_message: result.data._error_message})
|
||||
|
||||
@confirm.notify("error", errorMsg)
|
||||
|
||||
getRestrictionError: (result) ->
|
||||
if result.headers
|
||||
errorKey = ''
|
||||
|
||||
user = @currentUserService.getUser()
|
||||
maxMemberships = null
|
||||
|
||||
if result.headers.isPrivate
|
||||
privateError = !@currentUserService.canCreatePrivateProjects().valid
|
||||
|
||||
if user.get('max_memberships_private_projects') != null && result.headers.memberships >= user.get('max_memberships_private_projects')
|
||||
membersError = true
|
||||
else
|
||||
membersError = false
|
||||
|
||||
if privateError && membersError
|
||||
errorKey = 'private-space-members'
|
||||
maxMemberships = user.get('max_memberships_private_projects')
|
||||
else if privateError
|
||||
errorKey = 'private-space'
|
||||
else if membersError
|
||||
errorKey = 'private-members'
|
||||
maxMemberships = user.get('max_memberships_private_projects')
|
||||
|
||||
else
|
||||
publicError = !@currentUserService.canCreatePublicProjects().valid
|
||||
|
||||
if user.get('max_memberships_public_projects') != null && result.headers.memberships >= user.get('max_memberships_public_projects')
|
||||
membersError = true
|
||||
else
|
||||
membersError = false
|
||||
|
||||
if publicError && membersError
|
||||
errorKey = 'public-space-members'
|
||||
maxMemberships = user.get('max_memberships_public_projects')
|
||||
else if publicError
|
||||
errorKey = 'public-space'
|
||||
else if membersError
|
||||
errorKey = 'public-members'
|
||||
maxMemberships = user.get('max_memberships_public_projects')
|
||||
|
||||
if !errorKey
|
||||
return false
|
||||
|
||||
return {
|
||||
key: errorKey,
|
||||
values: {
|
||||
max_memberships: maxMemberships,
|
||||
members: result.headers.memberships
|
||||
}
|
||||
}
|
||||
else
|
||||
return false
|
||||
|
||||
angular.module("taigaProjects").service("tgImportProjectService", ImportProjectService)
|
|
@ -0,0 +1,294 @@
|
|||
###
|
||||
# Copyright (C) 2014-2015 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: import-project.service.spec.coffee
|
||||
###
|
||||
|
||||
describe "tgImportProjectService", ->
|
||||
$provide = null
|
||||
importProjectService = null
|
||||
mocks = {}
|
||||
|
||||
_mockCurrentUserService = ->
|
||||
mocks.currentUserService = {
|
||||
loadProjects: sinon.stub(),
|
||||
getUser: sinon.stub(),
|
||||
canCreatePrivateProjects: sinon.stub(),
|
||||
canCreatePublicProjects: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgCurrentUserService", mocks.currentUserService)
|
||||
|
||||
_mockAuth = ->
|
||||
mocks.auth = {
|
||||
refresh: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$tgAuth", mocks.auth)
|
||||
|
||||
_mockLightboxFactory = ->
|
||||
mocks.lightboxFactory = {
|
||||
create: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("tgLightboxFactory", mocks.lightboxFactory)
|
||||
|
||||
_mockTranslate = ->
|
||||
mocks.translate = {
|
||||
instant: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$translate", mocks.translate)
|
||||
|
||||
_mockConfirm = ->
|
||||
mocks.confirm = {
|
||||
success: sinon.stub(),
|
||||
notify: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$tgConfirm", mocks.confirm)
|
||||
|
||||
_mockLocation = ->
|
||||
mocks.location = {
|
||||
path: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$location", mocks.location)
|
||||
|
||||
_mockNavUrls = ->
|
||||
mocks.navUrls = {
|
||||
resolve: sinon.stub()
|
||||
}
|
||||
|
||||
$provide.value("$tgNavUrls", mocks.navUrls)
|
||||
|
||||
_mocks = ->
|
||||
module (_$provide_) ->
|
||||
$provide = _$provide_
|
||||
|
||||
_mockCurrentUserService()
|
||||
_mockAuth()
|
||||
_mockLightboxFactory()
|
||||
_mockTranslate()
|
||||
_mockConfirm()
|
||||
_mockLocation()
|
||||
_mockNavUrls()
|
||||
|
||||
return null
|
||||
|
||||
_inject = ->
|
||||
inject (_tgImportProjectService_) ->
|
||||
importProjectService = _tgImportProjectService_
|
||||
|
||||
_setup = ->
|
||||
_mocks()
|
||||
_inject()
|
||||
|
||||
beforeEach ->
|
||||
module "taigaProjects"
|
||||
|
||||
_setup()
|
||||
|
||||
it "import success async mode", (done) ->
|
||||
result = {
|
||||
status: 202,
|
||||
data: {
|
||||
slug: 'project-slug'
|
||||
}
|
||||
}
|
||||
|
||||
mocks.translate.instant.returns('xxx')
|
||||
|
||||
mocks.currentUserService.loadProjects.promise().resolve()
|
||||
|
||||
importProjectService.importSuccess(result).then () ->
|
||||
expect(mocks.confirm.success).have.been.calledOnce
|
||||
done()
|
||||
|
||||
it "import success sync mode", (done) ->
|
||||
result = {
|
||||
status: 201,
|
||||
data: {
|
||||
slug: 'project-slug'
|
||||
}
|
||||
}
|
||||
|
||||
mocks.translate.instant.returns('msg')
|
||||
|
||||
mocks.navUrls.resolve.withArgs('project-admin-project-profile-details', {project: 'project-slug'}).returns('url')
|
||||
|
||||
mocks.currentUserService.loadProjects.promise().resolve()
|
||||
|
||||
importProjectService.importSuccess(result).then () ->
|
||||
expect(mocks.location.path).have.been.calledWith('url')
|
||||
expect(mocks.confirm.notify).have.been.calledWith('success', 'msg')
|
||||
done()
|
||||
|
||||
it "private get restriction errors, private & member error", () ->
|
||||
result = {
|
||||
headers: {
|
||||
isPrivate: true,
|
||||
memberships: 10
|
||||
}
|
||||
}
|
||||
|
||||
mocks.currentUserService.getUser.returns(Immutable.fromJS({
|
||||
max_memberships_private_projects: 1
|
||||
}))
|
||||
|
||||
mocks.currentUserService.canCreatePrivateProjects.returns({
|
||||
valid: false
|
||||
})
|
||||
|
||||
error = importProjectService.getRestrictionError(result)
|
||||
|
||||
expect(error).to.be.eql({
|
||||
key: 'private-space-members',
|
||||
values: {
|
||||
max_memberships: 1,
|
||||
members: 10
|
||||
}
|
||||
})
|
||||
|
||||
it "private get restriction errors, private limit error", () ->
|
||||
result = {
|
||||
headers: {
|
||||
isPrivate: true,
|
||||
memberships: 10
|
||||
}
|
||||
}
|
||||
|
||||
mocks.currentUserService.getUser.returns(Immutable.fromJS({
|
||||
max_memberships_private_projects: 20
|
||||
}))
|
||||
|
||||
mocks.currentUserService.canCreatePrivateProjects.returns({
|
||||
valid: false
|
||||
})
|
||||
|
||||
error = importProjectService.getRestrictionError(result)
|
||||
|
||||
expect(error).to.be.eql({
|
||||
key: 'private-space',
|
||||
values: {
|
||||
max_memberships: null,
|
||||
members: 10
|
||||
}
|
||||
})
|
||||
|
||||
it "private get restriction errors, members error", () ->
|
||||
result = {
|
||||
headers: {
|
||||
isPrivate: true,
|
||||
memberships: 10
|
||||
}
|
||||
}
|
||||
|
||||
mocks.currentUserService.getUser.returns(Immutable.fromJS({
|
||||
max_memberships_private_projects: 1
|
||||
}))
|
||||
|
||||
mocks.currentUserService.canCreatePrivateProjects.returns({
|
||||
valid: true
|
||||
})
|
||||
|
||||
error = importProjectService.getRestrictionError(result)
|
||||
|
||||
expect(error).to.be.eql({
|
||||
key: 'private-members',
|
||||
values: {
|
||||
max_memberships: 1,
|
||||
members: 10
|
||||
}
|
||||
})
|
||||
|
||||
it "public get restriction errors, public & member error", () ->
|
||||
result = {
|
||||
headers: {
|
||||
isPrivate: false,
|
||||
memberships: 10
|
||||
}
|
||||
}
|
||||
|
||||
mocks.currentUserService.getUser.returns(Immutable.fromJS({
|
||||
max_memberships_public_projects: 1
|
||||
}))
|
||||
|
||||
mocks.currentUserService.canCreatePublicProjects.returns({
|
||||
valid: false
|
||||
})
|
||||
|
||||
error = importProjectService.getRestrictionError(result)
|
||||
|
||||
expect(error).to.be.eql({
|
||||
key: 'public-space-members',
|
||||
values: {
|
||||
max_memberships: 1,
|
||||
members: 10
|
||||
}
|
||||
})
|
||||
|
||||
it "public get restriction errors, public limit error", () ->
|
||||
result = {
|
||||
headers: {
|
||||
isPrivate: false,
|
||||
memberships: 10
|
||||
}
|
||||
}
|
||||
|
||||
mocks.currentUserService.getUser.returns(Immutable.fromJS({
|
||||
max_memberships_public_projects: 20
|
||||
}))
|
||||
|
||||
mocks.currentUserService.canCreatePublicProjects.returns({
|
||||
valid: false
|
||||
})
|
||||
|
||||
error = importProjectService.getRestrictionError(result)
|
||||
|
||||
expect(error).to.be.eql({
|
||||
key: 'public-space',
|
||||
values: {
|
||||
max_memberships: null,
|
||||
members: 10
|
||||
}
|
||||
})
|
||||
|
||||
it "public get restriction errors, members error", () ->
|
||||
result = {
|
||||
headers: {
|
||||
isPrivate: false,
|
||||
memberships: 10
|
||||
}
|
||||
}
|
||||
|
||||
mocks.currentUserService.getUser.returns(Immutable.fromJS({
|
||||
max_memberships_public_projects: 1
|
||||
}))
|
||||
|
||||
mocks.currentUserService.canCreatePublicProjects.returns({
|
||||
valid: true
|
||||
})
|
||||
|
||||
error = importProjectService.getRestrictionError(result)
|
||||
|
||||
expect(error).to.be.eql({
|
||||
key: 'public-members',
|
||||
values: {
|
||||
max_memberships: 1,
|
||||
members: 10
|
||||
}
|
||||
})
|
|
@ -0,0 +1,26 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: invite-members.controller.coffee
|
||||
###
|
||||
|
||||
class InviteMembersController
|
||||
@.$inject = []
|
||||
|
||||
isDisabled: (id) ->
|
||||
return @.invitedMembers.indexOf(id) == -1
|
||||
|
||||
angular.module("taigaProjects").controller("InviteMembersCtrl", InviteMembersController)
|
|
@ -0,0 +1,38 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: invite-members.directive.coffee
|
||||
###
|
||||
|
||||
InviteMembersDirective = () ->
|
||||
link = (scope, el, attr, ctrl) ->
|
||||
|
||||
return {
|
||||
link: link,
|
||||
templateUrl:"projects/create/invite-members/invite-members.html",
|
||||
controller: "InviteMembersCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {
|
||||
invitedMembers: '<',
|
||||
members: '<',
|
||||
onToggleInvitedMember: '&'
|
||||
}
|
||||
}
|
||||
|
||||
InviteMembersDirective.$inject = []
|
||||
|
||||
angular.module("taigaProjects").directive("tgInviteMembers", InviteMembersDirective)
|
|
@ -0,0 +1,8 @@
|
|||
fieldset.create-project-invite
|
||||
.create-project-invite-avatars
|
||||
tg-single-member(
|
||||
tg-repeat="member in vm.members track by member.get('id')"
|
||||
disabled="vm.isDisabled(member.get('id'))"
|
||||
avatar="member"
|
||||
ng-click="vm.onToggleInvitedMember({member: member.get('id')})"
|
||||
)
|
|
@ -0,0 +1,7 @@
|
|||
.create-project-invite {
|
||||
&-avatars {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: single-member.directive.coffee
|
||||
###
|
||||
|
||||
SingleMemberDirective = () ->
|
||||
return {
|
||||
templateUrl:"projects/create/invite-members/single-member/single-member.html",
|
||||
scope: {
|
||||
disabled: "<",
|
||||
avatar: "="
|
||||
}
|
||||
}
|
||||
|
||||
SingleMemberDirective.$inject = []
|
||||
|
||||
angular.module("taigaProjects").directive("tgSingleMember", SingleMemberDirective)
|
|
@ -0,0 +1,6 @@
|
|||
label.create-project-invite-avatar(ng-class="{'disabled': disabled}")
|
||||
img(
|
||||
tg-avatar="avatar"
|
||||
alt="{{avatar.get('full_name_display')}}"
|
||||
title="{{avatar.get('full_name_display')}}"
|
||||
)
|
|
@ -0,0 +1,40 @@
|
|||
.create-project-invite-avatar {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-right: .25rem;
|
||||
&:hover {
|
||||
@include empty-color(47);
|
||||
border: 0;
|
||||
opacity: .9;
|
||||
transition: all .2s;
|
||||
transition-delay: .2s;
|
||||
}
|
||||
&.disabled {
|
||||
opacity: .3;
|
||||
transition: opacity .2s;
|
||||
&:hover {
|
||||
@include empty-color(23);
|
||||
border: 0;
|
||||
opacity: .6;
|
||||
transition: all .2s ease-in;
|
||||
&::after {
|
||||
background: $grayer;
|
||||
left: 24px;
|
||||
top: 8px;
|
||||
transform: rotate(0);
|
||||
transform-origin: center;
|
||||
}
|
||||
&::before {
|
||||
background: $grayer;
|
||||
right: 22px;
|
||||
top: 8px;
|
||||
transform: rotate(90deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
img {
|
||||
cursor: pointer;
|
||||
width: 3rem;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: jira-import-project-form.controller.coffee
|
||||
###
|
||||
|
||||
class JiraImportProjectFormController
|
||||
@.$inject = [
|
||||
"tgCurrentUserService"
|
||||
]
|
||||
|
||||
constructor: (@currentUserService) ->
|
||||
@.canCreatePublicProjects = @currentUserService.canCreatePublicProjects()
|
||||
@.canCreatePrivateProjects = @currentUserService.canCreatePrivateProjects()
|
||||
|
||||
@.projectForm = @.project.toJS()
|
||||
|
||||
@.projectForm.is_private = false
|
||||
@.projectForm.keepExternalReference = false
|
||||
if @.projectForm.importer_type == "agile"
|
||||
@.projectForm.project_type = null
|
||||
else
|
||||
@.projectForm.project_type = "scrum"
|
||||
@.projectForm.create_subissues = true
|
||||
|
||||
if !@.canCreatePublicProjects.valid && @.canCreatePrivateProjects.valid
|
||||
@.projectForm.is_private = true
|
||||
|
||||
checkUsersLimit: () ->
|
||||
@.limitMembersPrivateProject = @currentUserService.canAddMembersPrivateProject(@.members.size)
|
||||
@.limitMembersPublicProject = @currentUserService.canAddMembersPublicProject(@.members.size)
|
||||
|
||||
saveForm: () ->
|
||||
@.onSaveProjectDetails({project: Immutable.fromJS(@.projectForm)})
|
||||
|
||||
canCreateProject: () ->
|
||||
if @.projectForm.is_private
|
||||
return @.canCreatePrivateProjects.valid
|
||||
else
|
||||
return @.canCreatePublicProjects.valid
|
||||
|
||||
isDisabled: () ->
|
||||
return !@.canCreateProject()
|
||||
|
||||
angular.module('taigaProjects').controller('JiraImportProjectFormCtrl', JiraImportProjectFormController)
|
|
@ -0,0 +1,40 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: jira-import-project-form.directive.coffee
|
||||
###
|
||||
|
||||
JiraImportProjectFormDirective = () ->
|
||||
return {
|
||||
link: (scope, elm, attr, ctrl) ->
|
||||
scope.$watch('vm.members', ctrl.checkUsersLimit.bind(ctrl))
|
||||
|
||||
templateUrl:"projects/create/jira-import/jira-import-project-form/jira-import-project-form.html",
|
||||
controller: "JiraImportProjectFormCtrl",
|
||||
controllerAs: "vm",
|
||||
bindToController: true,
|
||||
scope: {
|
||||
members: '<',
|
||||
project: '<',
|
||||
onSaveProjectDetails: '&',
|
||||
onCancelForm: '&',
|
||||
fetchingUsers: '<'
|
||||
}
|
||||
}
|
||||
|
||||
JiraImportProjectFormDirective.$inject = []
|
||||
|
||||
angular.module("taigaProjects").directive("tgJiraImportProjectForm", JiraImportProjectFormDirective)
|
|
@ -0,0 +1,126 @@
|
|||
.import-project-jira-form
|
||||
div(ng-include="'projects/create/import/import-header.html'")
|
||||
|
||||
.spin(tg-loading="vm.fetchingUsers")
|
||||
|
||||
form(
|
||||
ng-if="!vm.fetchingUsers",
|
||||
name="projectForm",
|
||||
ng-submit="vm.saveForm()"
|
||||
)
|
||||
div(ng-include="'projects/create/import-project-form-common/name.html'")
|
||||
div(ng-include="'projects/create/import-project-form-common/description.html'")
|
||||
|
||||
.create-project-import-type(role="group", ng-if="vm.projectForm.importer_type !== 'agile'")
|
||||
fieldset
|
||||
input(
|
||||
type="radio"
|
||||
name="project_type"
|
||||
id="template-scrum"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="'scrum'"
|
||||
ng-model="vm.projectForm.project_type"
|
||||
required
|
||||
)
|
||||
label(for="template-scrum")
|
||||
tg-svg(svg-icon="icon-scrum")
|
||||
span(translate="PROJECT.IMPORT.JIRA.SCRUM_PROJECT")
|
||||
fieldset
|
||||
input(
|
||||
type="radio"
|
||||
name="project_type"
|
||||
id="template-kanban"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="'kanban'"
|
||||
ng-model="vm.projectForm.project_type"
|
||||
required
|
||||
)
|
||||
label(for="template-kanban")
|
||||
tg-svg(svg-icon="icon-kanban")
|
||||
span(translate="PROJECT.IMPORT.JIRA.KANBAN_PROJECT")
|
||||
fieldset
|
||||
input(
|
||||
type="radio"
|
||||
name="project_type"
|
||||
id="template-issues"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="'issues'"
|
||||
ng-model="vm.projectForm.project_type"
|
||||
required
|
||||
)
|
||||
label(for="template-issues")
|
||||
tg-svg(svg-icon="icon-issues")
|
||||
span(translate="PROJECT.IMPORT.JIRA.ISSUES_PROJECT")
|
||||
|
||||
p.create-project-import-type-info(
|
||||
ng-if="vm.projectForm.project_type == 'scrum'"
|
||||
translate='PROJECT.IMPORT.JIRA.CREATE_AS_SCRUM_DESCRIPTION'
|
||||
)
|
||||
p.create-project-import-type-info(
|
||||
ng-if="vm.projectForm.project_type == 'kanban'"
|
||||
translate='PROJECT.IMPORT.JIRA.CREATE_AS_KANBAN_DESCRIPTION'
|
||||
)
|
||||
.create-project-type-issues-subform(ng-if="vm.projectForm.project_type == 'issues'")
|
||||
p.create-project-type-issues-subform-title(
|
||||
translate='PROJECT.IMPORT.JIRA.CREATE_AS_ISSUES_DESCRIPTION'
|
||||
)
|
||||
fieldset.create-project-type-issues-subform-radiogr
|
||||
label
|
||||
input(
|
||||
type="radio"
|
||||
name="create_subissues"
|
||||
id="template-issues-create"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="true"
|
||||
ng-model="vm.projectForm.create_subissues"
|
||||
required
|
||||
)
|
||||
svg(
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
)
|
||||
circle(
|
||||
cx="7"
|
||||
cy="7"
|
||||
r="6"
|
||||
)
|
||||
span.control-indicator(translate="PROJECT.IMPORT.JIRA.CREATE_NEW_ISSUES")
|
||||
label
|
||||
input(
|
||||
type="radio"
|
||||
name="create_subissues"
|
||||
id="template-issues-ignore"
|
||||
data-required="true"
|
||||
aria-hidden="true"
|
||||
ng-value="false"
|
||||
ng-model="vm.projectForm.create_subissues"
|
||||
required
|
||||
)
|
||||
svg(
|
||||
viewBox="0 0 16 16"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
)
|
||||
circle(
|
||||
cx="7"
|
||||
cy="7"
|
||||
r="6"
|
||||
)
|
||||
span.control-indicator(translate="PROJECT.IMPORT.JIRA.NOT_CREATE_NEW_ISSUES")
|
||||
|
||||
div(ng-include="'projects/create/import-project-form-common/project-privacy.html'")
|
||||
tg-create-project-restrictions(
|
||||
is-private="vm.projectForm.is_private"
|
||||
can-create-public-projects="vm.canCreatePublicProjects"
|
||||
can-create-private-projects="vm.canCreatePrivateProjects"
|
||||
)
|
||||
tg-create-project-members-restrictions(
|
||||
is-private="vm.projectForm.is_private"
|
||||
limit-members-private-project="vm.limitMembersPrivateProject"
|
||||
limit-members-public-project="vm.limitMembersPublicProject"
|
||||
)
|
||||
div(ng-include="'projects/create/import-project-form-common/links.html'")
|
||||
div(ng-include="'projects/create/import-project-form-common/actions.html'")
|
|
@ -0,0 +1,30 @@
|
|||
.import-project-jira-form {
|
||||
@include create-project;
|
||||
}
|
||||
|
||||
.create-project-import-type-info {
|
||||
@include font-size(small);
|
||||
@include font-type(light);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.create-project-type-issues-subform {
|
||||
margin: 1rem 0 2rem;
|
||||
|
||||
&-title {
|
||||
@include font-size(small);
|
||||
@include font-type(bold);
|
||||
}
|
||||
|
||||
&-radiogr {
|
||||
@include radio-group;
|
||||
}
|
||||
}
|
||||
|
||||
.create-project-import-type {
|
||||
margin-bottom: .25rem;
|
||||
|
||||
fieldset {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
###
|
||||
# Copyright (C) 2014-2016 Taiga Agile LLC <taiga@taiga.io>
|
||||
#
|
||||
# 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: jira-import.controller.coffee
|
||||
###
|
||||
|
||||
class JiraImportController
|
||||
@.$inject = [
|
||||
'tgJiraImportService',
|
||||
'$tgConfirm',
|
||||
'$translate',
|
||||
'tgImportProjectService',
|
||||
]
|
||||
|
||||
constructor: (@jiraImportService, @confirm, @translate, @importProjectService) ->
|
||||
@.step = 'autorization-jira'
|
||||
@.project = null
|
||||
taiga.defineImmutableProperty @, 'projects', () => return @jiraImportService.projects
|
||||
taiga.defineImmutableProperty @, 'members', () => return @jiraImportService.projectUsers
|
||||
|
||||
startProjectSelector: () ->
|
||||
@.step = 'project-select-jira'
|
||||
@jiraImportService.fetchProjects()
|
||||
|
||||
onSelectProject: (project) ->
|
||||
@.step = 'project-form-jira'
|
||||
@.project = project
|
||||
@.fetchingUsers = true
|
||||
|
||||
@jiraImportService.fetchUsers(@.project.get('id')).then () => @.fetchingUsers = false
|
||||
|
||||
onSaveProjectDetails: (project) ->
|
||||
@.project = project
|
||||
@.step = 'project-members-jira'
|
||||
|
||||
onCancelMemberSelection: () ->
|
||||
@.step = 'project-form-jira'
|
||||
|
||||
startImport: (users) ->
|
||||
loader = @confirm.loader(@translate.instant('PROJECT.IMPORT.IN_PROGRESS.TITLE'), @translate.instant('PROJECT.IMPORT.IN_PROGRESS.DESCRIPTION'), true)
|
||||
|
||||
loader.start()
|
||||
|
||||
projectType = @.project.get('project_type')
|
||||
if projectType == "issues" and @.project.get('create_subissues')
|
||||
projectType = "issues-with-subissues"
|
||||
|
||||
promise = @jiraImportService.importProject(
|
||||
@.project.get('name'),
|
||||
@.project.get('description'),
|
||||
@.project.get('id'),
|
||||
users,
|
||||
@.project.get('keepExternalReference'),
|
||||
@.project.get('is_private'),
|
||||
projectType,
|
||||
@.project.get('importer_type'),
|
||||
)
|
||||
|
||||
@importProjectService.importPromise(promise).then () => loader.stop()
|
||||
|
||||
submitUserSelection: (users) ->
|
||||
@.startImport(users)
|
||||
return null
|
||||
|
||||
angular.module('taigaProjects').controller('JiraImportCtrl', JiraImportController)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue