doortts doortts 2017-01-19
reviews: Support reviews items to download as xls file
See: Yona github issue #90
@bf37fd2f58b4c51aa59410bccc213080194f0105
app/controllers/ReviewThreadApp.java
--- app/controllers/ReviewThreadApp.java
+++ app/controllers/ReviewThreadApp.java
@@ -1,43 +1,37 @@
 /**
- * Yobi, Project Hosting SW
- *
- * Copyright 2013 NAVER Corp.
- * http://yobi.io
- *
- * @author Changsung Kim, Keesun Baik
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+ * Yona, 21st Century Project Hosting SW
+ * <p>
+ * Copyright Yona & Yobi Authors & NAVER Corp.
+ * https://yona.io
+ **/
 package controllers;
 
 import com.avaje.ebean.ExpressionList;
 import com.avaje.ebean.Page;
 import controllers.annotation.AnonymousCheck;
 import controllers.annotation.IsAllowed;
-import models.CommentThread;
-import models.Project;
+import jxl.Workbook;
+import jxl.format.ScriptStyle;
+import jxl.format.UnderlineStyle;
+import jxl.write.*;
+import models.*;
 import models.enumeration.Operation;
 import models.support.ReviewSearchCondition;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.tika.Tika;
 import play.data.Form;
 import play.db.ebean.Transactional;
 import play.mvc.Controller;
 import play.mvc.Result;
+import utils.HttpUtil;
+import utils.JodaDateUtil;
 import views.html.reviewthread.list;
 
-/**
- * @author Changsung Kim
- * @author Keesun Baik
- */
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Date;
+import java.util.List;
+
 @AnonymousCheck
 public class ReviewThreadApp extends Controller {
 
@@ -50,8 +44,129 @@
         Project project = Project.findByOwnerAndProjectName(ownerName, projectName);
         ReviewSearchCondition searchCondition = Form.form(ReviewSearchCondition.class).bindFromRequest().get();
         ExpressionList<CommentThread> el = searchCondition.asExpressionList(project);
+        if ("xls".equals(request().getQueryString("format"))) {
+            return reviewThreadsDownload(project, el);
+        }
         Page<CommentThread> commentThreads = el.findPagingList(REVIEWS_PER_PAGE).getPage(searchCondition.pageNum - 1);
         return ok(list.render(project, commentThreads, searchCondition));
     }
 
+    private static Result reviewThreadsDownload(Project project, ExpressionList<CommentThread> el) {
+        List<CommentThread> commentThreads = el.findList();
+
+        String filename = null;
+        byte[] excelData = null;
+        try {
+            excelData = excelFrom(commentThreads);
+            filename = HttpUtil.encodeContentDisposition(
+                    project.name + "_reviews_" + JodaDateUtil.getDateStringWithoutSpace(new Date()) + ".xls");
+        } catch (WriteException | IOException e) {
+            e.printStackTrace();
+        }
+
+        response().setHeader("Content-Type", new Tika().detect(filename));
+        response().setHeader("Content-Disposition", "attachment; " + filename);
+
+        assert excelData != null;
+        return ok(excelData);
+    }
+
+    public static byte[] excelFrom(List<CommentThread> commentThreads) throws WriteException, IOException {
+        WritableWorkbook workbook;
+        WritableSheet sheet;
+
+        WritableCellFormat headerCellFormat = getHeaderCellFormat();
+        WritableCellFormat bodyCellFormat = getBodyCellFormat();
+        WritableCellFormat dateCellFormat = getDateCellFormat();
+
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        workbook = Workbook.createWorkbook(bos);
+        sheet = workbook.createSheet(String.valueOf(JodaDateUtil.today().getTime()), 0);
+
+        String[] titles = {"No", "COMMIT ID", "REVIEW ID", "REVIEW TITLE", "Thread Author", "Response Text", "Response", "REVIEW STATE", "is PullRequest?", "Date"};
+
+        for (int i = 0; i < titles.length; i++) {
+            sheet.addCell(new jxl.write.Label(i, 0, titles[i], headerCellFormat));
+            sheet.setColumnView(i, 20);
+        }
+
+        int rowNumber = 0;
+        for (int idx = 0; idx < commentThreads.size(); idx++) {
+            CommentThread commentThread = commentThreads.get(idx);
+            String commitId = "";
+            if ( commentThread instanceof NonRangedCodeCommentThread){
+                commitId = ((NonRangedCodeCommentThread) commentThread).commitId;
+            } else {
+                commitId = ((CodeCommentThread) commentThread).commitId;
+            }
+            String threadFirstComment = commentThread.getFirstReviewComment().getContents();
+            for (int j = 0; j < commentThread.reviewComments.size(); j++) {
+                ReviewComment comment = commentThread.reviewComments.get(j);
+                int columnPos = 0;
+                String reponseComment = threadFirstComment.equals(comment.getContents())? "":comment.getContents();
+                sheet.addCell(new jxl.write.Label(columnPos++, rowNumber + 1, "" + (rowNumber + 1), bodyCellFormat));
+                sheet.addCell(new jxl.write.Label(columnPos++, rowNumber + 1, commitId.substring(0, 7), bodyCellFormat));
+                sheet.addCell(new jxl.write.Label(columnPos++, rowNumber + 1, commentThread.id.toString(), bodyCellFormat));
+                sheet.addCell(new jxl.write.Label(columnPos++, rowNumber + 1, StringUtils.isEmpty(reponseComment)?threadFirstComment:"", bodyCellFormat));
+                sheet.addCell(new jxl.write.Label(columnPos++, rowNumber + 1, StringUtils.isEmpty(reponseComment)?commentThread.author.name:"", bodyCellFormat));
+                sheet.addCell(new jxl.write.Label(columnPos++, rowNumber + 1, reponseComment, bodyCellFormat));
+                sheet.addCell(new jxl.write.Label(columnPos++, rowNumber + 1, StringUtils.isNotEmpty(reponseComment)?comment.author.name:"", bodyCellFormat));
+                sheet.addCell(new jxl.write.Label(columnPos++, rowNumber + 1, commentThread.state.toString(), bodyCellFormat));
+                sheet.addCell(new jxl.write.Label(columnPos++, rowNumber + 1, "" + commentThread.isOnPullRequest(), bodyCellFormat));
+                sheet.addCell(new jxl.write.DateTime(columnPos++, rowNumber + 1, comment.createdDate, dateCellFormat));
+                rowNumber++;
+            }
+        }
+        workbook.write();
+
+        try {
+            workbook.close();
+        } catch (WriteException | IOException e) {
+            e.printStackTrace();
+        }
+
+        return bos.toByteArray();
+    }
+
+    private static String getIssueLabels(Issue issue) {
+        StringBuilder labels = new StringBuilder();
+        for(IssueLabel issueLabel: issue.getLabels()){
+            labels.append(issueLabel.name).append(", ");
+        }
+        return labels.toString().replaceAll(", $", "");
+    }
+
+    private static WritableCellFormat getDateCellFormat() throws WriteException {
+        WritableFont baseFont= new WritableFont(WritableFont.ARIAL, 12, WritableFont.NO_BOLD, false, UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.BLACK, ScriptStyle.NORMAL_SCRIPT);
+        DateFormat valueFormatDate = new DateFormat("yyyy-MM-dd HH:mm");
+        WritableCellFormat cellFormat = new WritableCellFormat(valueFormatDate);
+        cellFormat.setFont(baseFont);
+        cellFormat.setShrinkToFit(true);
+        cellFormat.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.THIN);
+        cellFormat.setAlignment(jxl.format.Alignment.CENTRE);
+        cellFormat.setVerticalAlignment(jxl.format.VerticalAlignment.TOP);
+        return cellFormat;
+    }
+
+    private static WritableCellFormat getBodyCellFormat() throws WriteException {
+        WritableFont baseFont = new WritableFont(WritableFont.ARIAL, 12, WritableFont.NO_BOLD, false, UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.BLACK, ScriptStyle.NORMAL_SCRIPT);
+        return getBodyCellFormat(baseFont);
+    }
+
+    private static WritableCellFormat getBodyCellFormat(WritableFont baseFont) throws WriteException {
+        WritableCellFormat cellFormat = new WritableCellFormat(baseFont);
+        cellFormat.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.THIN);
+        cellFormat.setWrap(true);
+        cellFormat.setVerticalAlignment(jxl.format.VerticalAlignment.TOP);
+        return cellFormat;
+    }
+
+    private static WritableCellFormat getHeaderCellFormat() throws WriteException {
+        WritableFont headerFont = new WritableFont(WritableFont.ARIAL, 14, WritableFont.BOLD, false,
+                UnderlineStyle.NO_UNDERLINE, jxl.format.Colour.BLACK, ScriptStyle.NORMAL_SCRIPT);
+        WritableCellFormat headerCell = new WritableCellFormat(headerFont);
+        headerCell.setBorder(jxl.format.Border.ALL, jxl.format.BorderLineStyle.DOUBLE);
+        headerCell.setAlignment(jxl.format.Alignment.CENTRE);
+        return headerCell;
+    }
 }
app/models/Issue.java
--- app/models/Issue.java
+++ app/models/Issue.java
@@ -1,23 +1,9 @@
 /**
- * Yobi, Project Hosting SW
- *
- * Copyright 2012 NAVER Corp.
- * http://yobi.io
- *
- * @author yoon
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+ * Yona, 21st Century Project Hosting SW
+ * <p>
+ * Copyright Yona & Yobi Authors & NAVER Corp.
+ * https://yona.io
+ **/
 package models;
 
 import com.avaje.ebean.Ebean;
@@ -36,6 +22,7 @@
 import models.enumeration.State;
 import models.resource.Resource;
 import models.support.SearchCondition;
+import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.time.DateUtils;
 import org.apache.shiro.util.CollectionUtils;
 import play.data.Form;
@@ -55,9 +42,6 @@
 @Entity
 @Table(uniqueConstraints = @UniqueConstraint(columnNames = {"project_id", "number"}))
 public class Issue extends AbstractPosting implements LabelOwner {
-    /**
-     * @author Yobi TEAM
-     */
     private static final long serialVersionUID = -2409072006294045262L;
 
     public static final Finder<Long, Issue> finder = new Finder<>(Long.class, Issue.class);
@@ -238,7 +222,7 @@
         workbook = Workbook.createWorkbook(bos);
         sheet = workbook.createSheet(String.valueOf(JodaDateUtil.today().getTime()), 0);
 
-        String[] titles = {"No", Messages.get("issue.state"), Messages.get("title"), Messages.get("issue.assignee"), Messages.get("issue.label"), "Date", "Link"};
+        String[] titles = {"No", Messages.get("issue.state"), Messages.get("title"), Messages.get("issue.assignee"), Messages.get("issue.label"), "Created", "Due Date", "Source"};
 
         for (int i = 0; i < titles.length; i++) {
             sheet.addCell(new jxl.write.Label(i, 0, titles[i], headerCellFormat));
@@ -253,15 +237,14 @@
             sheet.addCell(new jxl.write.Label(columnPos++, idx, getAssigneeName(issue.assignee), bodyCellFormat));
             sheet.addCell(new jxl.write.Label(columnPos++, idx, getIssueLabels(issue), bodyCellFormat));
             sheet.addCell(new jxl.write.DateTime(columnPos++, idx, issue.createdDate, dateCellFormat));
+            sheet.addCell(new jxl.write.Label(columnPos++, idx, JodaDateUtil.geYMDDate(issue.dueDate), bodyCellFormat));
             sheet.addCell(new jxl.write.Label(columnPos++, idx, controllers.routes.IssueApp.issue(issue.project.owner, issue.project.name, issue.number).toString(), bodyCellFormat));
         }
         workbook.write();
 
         try {
             workbook.close();
-        } catch (WriteException e) {
-            e.printStackTrace();
-        } catch (IOException e) {
+        } catch (WriteException | IOException e) {
             e.printStackTrace();
         }
 
@@ -278,7 +261,7 @@
 
     private static WritableCellFormat getDateCellFormat() throws WriteException {
         WritableFont baseFont= new WritableFont(WritableFont.ARIAL, 12, WritableFont.NO_BOLD, false, UnderlineStyle.NO_UNDERLINE, Colour.BLACK, ScriptStyle.NORMAL_SCRIPT);
-        DateFormat valueFormatDate = new DateFormat("yyyy-MM-dd HH:mm:ss");
+        DateFormat valueFormatDate = new DateFormat("yyyy-MM-dd HH:mm");
         WritableCellFormat cellFormat = new WritableCellFormat(valueFormatDate);
         cellFormat.setFont(baseFont);
         cellFormat.setShrinkToFit(true);
app/utils/JodaDateUtil.java
--- app/utils/JodaDateUtil.java
+++ app/utils/JodaDateUtil.java
@@ -103,4 +103,21 @@
 
         return momentFromNow(date, Locale.getDefault().getLanguage());
     }
+
+    public static String getDateStringWithoutSpace(Date date){
+        if (date == null) {
+            date = new Date();
+        }
+        DateTime dateTime = new DateTime(date);
+        return dateTime.toString("yyyyMMddHHmm", Locale.getDefault());
+    }
+
+
+    public static String geYMDDate(Date date){
+        if (date == null) {
+            return "";
+        }
+        DateTime dateTime = new DateTime(date);
+        return dateTime.toString("yyyy-MM-dd", Locale.getDefault());
+    }
 }
app/views/reviewthread/list.scala.html
--- app/views/reviewthread/list.scala.html
+++ app/views/reviewthread/list.scala.html
@@ -1,27 +1,16 @@
 @**
- * Yobi, Project Hosting SW
- *
- * Copyright 2014 NAVER Corp.
- * http://yobi.io
- *
- * @author Keesun Baik
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- **@
-@(project:Project, page:com.avaje.ebean.Page[CommentThread], param:models.support.ReviewSearchCondition)
+* Yona, 21st Century Project Hosting SW
+*
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
+**@
+@import com.avaje.ebean.Page
+@import models.support.ReviewSearchCondition
+@(project:Project, page:Page[CommentThread], param:ReviewSearchCondition)
 @import views.html.helper.form
 @import utils.TemplateHelper._
 @import utils.JodaDateUtil
+@import utils.HttpUtil._
 
 @makeSortLink(sortField:String, sortFieldString:String) = {
     @if(param.orderBy == sortField) {
@@ -97,6 +86,11 @@
             <div class="review-list-wrap">
                 @partial_list(project, page, param)
             </div>
+            <div class="pull-left" style="padding:10px;">
+                <a href="@addQueryString(removeQueryString(requestHeader.uri, "pageNum"), "format=xls")" class="ybtn small">
+                    <i class="yobicon-file-excel"></i> @Messages("issue.downloadAsExcel")
+                </a>
+            </div>
             <div id="pagination"></div>
         </div>
     </div>
Add a comment
List