Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Add attachment export

  • Loading branch information...
commit fe706edeb349fbb5388b8eea2684faaedf672106 1 parent 79b6d0f
@vbarrier vbarrier authored
View
84 grails-app/controllers/org/icescrum/web/presentation/app/project/ProjectController.groovy
@@ -49,6 +49,8 @@ import org.icescrum.core.domain.Sprint
import org.icescrum.core.domain.User
import feedsplugin.FeedBuilder
import com.sun.syndication.io.SyndFeedOutput
+import org.codehaus.groovy.grails.web.util.StreamCharBuffer
+import org.apache.commons.io.FilenameUtils
@Secured('stakeHolder() or inProduct()')
class ProjectController {
@@ -418,17 +420,35 @@ class ProjectController {
render(status: 200, contentType: 'application/json', text: session.progress as JSON)
}
else if (params.get) {
+
+ def projectName = "${product.name.replaceAll("[^a-zA-Z\\s]", "").replaceAll(" ", "")}-${new Date().format('yyyy-MM-dd')}"
+ def zipFile = new File("${projectName}.zip")
+ def xml = new File("${projectName}.xml")
+
try {
- session.progress = new ProgressSupport()
session.progress.updateProgress(0, message(code: 'is.export.start'))
- response.setHeader "Content-disposition", "attachment; filename=${product.name.replaceAll("[^a-zA-Z\\s]", "").replaceAll(" ", "")}-${new Date().format('yyyy-MM-dd')}.xml"
- render(contentType: 'text/xml', template: '/project/xml', model: [object: product, deep: true, root: true], encoding: 'UTF-8')
+ StreamCharBuffer test = g.render(contentType: 'text/xml', template: '/project/xml', model: [object: product, deep: true, root: true], encoding: 'UTF-8')
+ xml.withWriter('UTF-8'){ out ->
+ test.writeTo(out)
+ }
+
+ def inputDir = new File(grailsApplication.config.icescrum.baseDir + File.separator + product.id)
+ ApplicationSupport.zipExportFile(zipFile,inputDir,xml)
+ ['Content-disposition': "attachment;filename=\"${projectName+'.zip'}\"",'Cache-Control': 'private','Pragma': ''].each {k, v ->
+ response.setHeader(k, v)
+ }
+ response.contentType = 'application/zip'
+ response.outputStream << zipFile.newInputStream()
session.progress?.completeProgress(message(code: 'is.export.complete'))
} catch (Exception e) {
if (log.debugEnabled) e.printStackTrace()
session.progress.progressError(message(code: 'is.export.error'))
+ } finally {
+ zipFile.delete()
+ xml.delete()
}
} else {
+ session.progress = new ProgressSupport()
render(template: 'dialogs/export')
}
}
@@ -450,7 +470,7 @@ class ProjectController {
def user = User.load(springSecurityService.principal.id)
if (params.cancel) {
- session.tmpP = null
+ session['import'] = null
session.progress = null
render(status: 200)
return
@@ -463,8 +483,23 @@ class ProjectController {
}
if (uploadedProject) {
session.progress = new ProgressSupport()
- session.tmpP = productService.parseXML(uploadedProject, session.progress)
- session.tmpXmlPath = uploadedProject.absolutePath
+ session['import'] = [:]
+ if (FilenameUtils.getExtension(uploadedProject.name) == 'xml'){
+ if (log.debugEnabled){ log.debug 'Export is an xml file, processing now' }
+ session['import']?.product = productService.parseXML(uploadedProject, session.progress)
+ session['import']?.path = uploadedProject.absolutePath
+ } else if (FilenameUtils.getExtension(uploadedProject.name) == 'zip'){
+ if (log.debugEnabled){ log.debug 'Export is a zipped file, unzipping now' }
+ def tmpDir = ApplicationSupport.createTempDir(FilenameUtils.getBaseName(uploadedProject.name))
+ ApplicationSupport.unzip(uploadedProject,tmpDir)
+ def xmlFile = tmpDir.listFiles().find { !it.isDirectory() && FilenameUtils.getExtension(it.name) == 'xml' }
+ if (xmlFile.exists()){
+ session['import']?.path = tmpDir.absolutePath
+ session['import']?.product = productService.parseXML(xmlFile, session.progress)
+ }else{
+ session.progress.progressError(message(code:'is.error'))
+ }
+ }
}
}
else if (params.status) {
@@ -478,22 +513,22 @@ class ProjectController {
session.progress = null
}
- if (session.tmpP) {
+ if (session['import']) {
def unValidableErrors = this.validateImport()
if (unValidableErrors) {
- session.tmpP = null
+ session['import'] = null
session.progress = null
render(status: 400, contentType: 'application/json', text: [notice: [text: unValidableErrors, type: 'error']] as JSON)
return
} else {
- def importMustChangeValues = session.tmpP.hasErrors() ?: (true in session.tmpP.teams*.hasErrors()) ?: (true in session.tmpP.getAllUsers()*.hasErrors())
+ def importMustChangeValues = session['import'].product.hasErrors() ?: (true in session['import'].product.teams*.hasErrors()) ?: (true in session['import'].product.getAllUsers()*.hasErrors())
render(template: 'dialogs/import', model: [
user: user,
- product: session.tmpP,
+ product: session['import'].product,
importMustChangeValues: importMustChangeValues,
- teamsErrors: session.tmpP.teams.findAll {it.hasErrors()},
- usersErrors: session.tmpP.getAllUsers().findAll {it.hasErrors()}
+ teamsErrors: session['import'].product.teams.findAll {it.hasErrors()},
+ usersErrors: session['import'].product.getAllUsers().findAll {it.hasErrors()}
])
}
} else {
@@ -510,13 +545,13 @@ class ProjectController {
}
}
- if (!session.tmpP) {
+ if (!session['import']) {
render(status: 400, contentType: 'application/json', text: [notice: [text: 'is.import.error.no.backup']] as JSON)
return
}
if (params.team?.name) {
- session.tmpP.teams.each {
+ session['import'].product.teams.each {
if (params.team.name."${it.uid}") {
it.name = params.team.name."${it.uid}"
}
@@ -524,7 +559,7 @@ class ProjectController {
}
if (params.user?.username) {
- session.tmpP.teams.each {
+ session['import'].product.teams.each {
it.members.each {it2 ->
if (params.user.username."${it2.uid}") {
it2.username = params.user.username."${it2.uid}"
@@ -539,7 +574,7 @@ class ProjectController {
}
if (params.productOwner?.username) {
- session.tmpP.productOwners.each {
+ session['import'].product.productOwners.each {
if (params.productOwner.username."${it.uid}") {
it.username = params.productOwner.username."${it.uid}"
}
@@ -550,10 +585,10 @@ class ProjectController {
if (params.productd?.int('erasableByUser')) {
erasableByUser = params.productd?.int('erasableByUser') ? true : false
}
- session.tmpP.erasableByUser = erasableByUser
+ session['import'].product.erasableByUser = erasableByUser
if (!erasableByUser && params.productd?.pkey != null && params.productd?.name != null) {
- session.tmpP.pkey = params.productd.pkey
- session.tmpP.name = params.productd.name
+ session['import'].product.pkey = params.productd.pkey
+ session['import'].product.name = params.productd.name
}
def errors = this.validateImport(true, erasableByUser)
if (errors) {
@@ -563,7 +598,7 @@ class ProjectController {
Product.withTransaction { status ->
try {
- productService.saveImport(session.tmpP, params.productd?.name, session.tmpXmlPath)
+ productService.saveImport(session['import'].product, params.productd?.name, session['import'].path)
} catch (IllegalStateException ise) {
status.setRollbackOnly()
render(status: 400, contentType: 'application/json', text: [notice: [text: message(code: ise.getMessage())]] as JSON)
@@ -575,15 +610,14 @@ class ProjectController {
render(status: 400, contentType: 'application/json', text: [notice: [text: message(code: 'is.import.error')]] as JSON)
return
}
- render(status:200, contentType:'application/json', text:session.tmpP as JSON)
- session.tmpP = null
- session.tmpXmlPath = null
+ render(status:200, contentType:'application/json', text:session['import'].product as JSON)
+ session['import'] = null
}
}
private def validateImport(def full = false, def erasableByUser = false) {
- def p = session.tmpP
+ def p = session['import'].product
productService.validate(p, session.progress)
def beansErrors = null
@@ -602,7 +636,7 @@ class ProjectController {
}
if (!pass) {
- beansErrors = renderErrors(bean: session.tmpP)
+ beansErrors = renderErrors(bean: session['import'].product)
} else if (p.errors) {
log.info("Product validation with warning (${p.name}): " + p.errors)
} else {
View
4 grails-app/taglib/org/icescrum/presentation/taglib/ExportTagLib.groovy
@@ -22,12 +22,14 @@
*/
package org.icescrum.presentation.taglib
+import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsHibernateUtil
+
class ExportTagLib {
static namespace = 'is'
def objectAsXML = { attrs,body ->
assert attrs.object
- pageScope.object = attrs.object
+ pageScope.object = GrailsHibernateUtil.unwrapIfProxy(attrs.object)
pageScope.propertiesObject = []
pageScope.listsObjects = []
pageScope.propertiesChildObject = []
View
14 grails-app/views/actor/_xml.gsp
@@ -21,6 +21,16 @@
--}%<is:objectAsXML object="${object}" node="actor" indentLevel="${indentLevel}" root="${root}">
<is:propertyAsXML name="['instances','expertnessLevel','useFrequency','creationDate']"/>
<is:propertyAsXML name="['name','satisfactionCriteria','notes','description']" cdata="true"/>
- <is:listAsXML name="stories" template="/export/xml/story" child="story" deep="${deep}"
- indentLevel="${indentLevel}"/>
+ <is:listAsXML
+ name="stories"
+ template="/xml/story"
+ child="story"
+ deep="${deep}"
+ indentLevel="${indentLevel + 1}"/>
+ <is:listAsXML
+ name="attachments"
+ template="/addons/attachmentXml"
+ child="attachment"
+ deep="${deep}"
+ indentLevel="${indentLevel + 1}"/>
</is:objectAsXML>
View
25 grails-app/views/addons/_attachmentXml.gsp
@@ -0,0 +1,25 @@
+%{--
+- Copyright (c) 2012 Kagilum SAS.
+-
+- This file is part of iceScrum.
+-
+- iceScrum 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.
+-
+- iceScrum 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 General Public License for more details.
+-
+- You should have received a copy of the GNU Affero General Public License
+- along with iceScrum. If not, see <http://www.gnu.org/licenses/>.
+-
+- Authors:
+-
+- Vincent Barrier (vbarrier@kagilum.com)
+--}%
+<is:objectAsXML object="${object}" node="attachment" indentLevel="${indentLevel}" root="${root}">
+ <is:propertyAsXML name="['ext','length','posterClass','posterId','dateCreated']"/>
+ <is:propertyAsXML name="['name','inputName','contentType']" cdata="true"/>
+</is:objectAsXML>
View
6 grails-app/views/feature/_xml.gsp
@@ -27,4 +27,10 @@
child="story"
deep="${deep}"
indentLevel="${indentLevel + 1}"/>
+ <is:listAsXML
+ name="attachments"
+ template="/addons/attachmentXml"
+ child="attachment"
+ deep="${deep}"
+ indentLevel="${indentLevel + 1}"/>
</is:objectAsXML>
View
8 grails-app/views/project/_xml.gsp
@@ -37,27 +37,27 @@
<is:listAsXML
name="releases"
child="release"
- deep="['release','sprint','task','cliche','story','comment','activity', 'acceptanceTest']"
+ deep="['release','sprint','task','cliche','story','comment','activity', 'acceptanceTest', 'attachment']"
template="/release/xml"/>
<% session.progress?.updateProgress(30, message(code: 'is.export.inprogress', args: [message(code: 'is.actor')])) %>
<is:listAsXML
name="actors"
child="actor"
template="/actor/xml"
- deep="['actor']"/>
+ deep="['actor','attachment']"/>
<% session.progress?.updateProgress(40, message(code: 'is.export.inprogress', args: [message(code: 'is.feature')])) %>
<is:listAsXML
name="features"
child="feature"
template="/feature/xml"
- deep="['feature']"/>
+ deep="['feature','attachment']"/>
<% session.progress?.updateProgress(50, message(code: 'is.export.inprogress', args: [message(code: 'is.story')])) %>
<is:listAsXML
expr="${{it.parentSprint == null}}"
name="stories"
template="/story/xml"
child="story"
- deep="['story','task','comment','activity', 'acceptanceTest']"/>
+ deep="['story','task','comment','activity','acceptanceTest','activity','attachment']"/>
<% session.progress?.updateProgress(80, message(code: 'is.export.inprogress', args: [message(code: 'is.cliche')])) %>
<is:listAsXML
name="cliches"
View
2  grails-app/views/project/dialogs/_import.gsp
@@ -30,7 +30,7 @@
<is:fieldFile noborder="true" label="is.dialog.importProject.choose.file">
<is:multiFilesUpload elementId="inportProductXml"
name="file"
- accept="['xml']"
+ accept="['xml','zip']"
urlUpload="${createLink(action:'upload',controller:'scrumOS')}"
multi="1"
params="[product:params.product]"
View
2  grails-app/views/sprint/_xml.gsp
@@ -25,6 +25,8 @@
<is:listAsXML
name="tasks"
template="/task/xml"
+ deep="${deep}"
+ child="task"
indentLevel="${indentLevel + 1}"
expr="${{it.parentStory == null}}"/>
<is:listAsXML
View
6 grails-app/views/story/_xml.gsp
@@ -50,4 +50,10 @@
child="acceptanceTest"
deep="${deep}"
indentLevel="${indentLevel + 1}"/>
+ <is:listAsXML
+ name="attachments"
+ template="/addons/attachmentXml"
+ child="attachment"
+ deep="${deep}"
+ indentLevel="${indentLevel + 1}"/>
</is:objectAsXML>
View
6 grails-app/views/task/_xml.gsp
@@ -23,4 +23,10 @@
<is:propertyAsXML object="creator"/>
<is:propertyAsXML object="responsible"/>
<is:propertyAsXML name="['name','description','notes']" cdata="true"/>
+ <is:listAsXML
+ name="attachments"
+ template="/addons/attachmentXml"
+ child="attachment"
+ deep="${deep}"
+ indentLevel="${indentLevel + 1}"/>
</is:objectAsXML>
Please sign in to comment.
Something went wrong with that request. Please try again.