Skip to content

Commit

Permalink
Add attachment export
Browse files Browse the repository at this point in the history
  • Loading branch information
vbarrier committed May 5, 2012
1 parent 79b6d0f commit fe706ed
Show file tree
Hide file tree
Showing 10 changed files with 124 additions and 33 deletions.
Expand Up @@ -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 {
Expand Down Expand Up @@ -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')
}
}
Expand All @@ -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
Expand All @@ -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) {
Expand All @@ -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 {
Expand All @@ -510,21 +545,21 @@ 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}"
}
}
}

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}"
Expand All @@ -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}"
}
Expand All @@ -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) {
Expand All @@ -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)
Expand All @@ -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

Expand All @@ -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 {
Expand Down
Expand Up @@ -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 = []
Expand Down
14 changes: 12 additions & 2 deletions grails-app/views/actor/_xml.gsp
Expand Up @@ -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>
25 changes: 25 additions & 0 deletions 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>
6 changes: 6 additions & 0 deletions grails-app/views/feature/_xml.gsp
Expand Up @@ -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>
8 changes: 4 additions & 4 deletions grails-app/views/project/_xml.gsp
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion grails-app/views/project/dialogs/_import.gsp
Expand Up @@ -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]"
Expand Down
2 changes: 2 additions & 0 deletions grails-app/views/sprint/_xml.gsp
Expand Up @@ -25,6 +25,8 @@
<is:listAsXML
name="tasks"
template="/task/xml"
deep="${deep}"
child="task"
indentLevel="${indentLevel + 1}"
expr="${{it.parentStory == null}}"/>
<is:listAsXML
Expand Down
6 changes: 6 additions & 0 deletions grails-app/views/story/_xml.gsp
Expand Up @@ -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>
6 changes: 6 additions & 0 deletions grails-app/views/task/_xml.gsp
Expand Up @@ -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>

0 comments on commit fe706ed

Please sign in to comment.