Wonjun Hong Wonjun Hong 2017-10-27
Group: Add pull request menu (#260)
@f8ed345f5b4a6f10ed362805f67a57850261074a
app/controllers/IssueApp.java
--- app/controllers/IssueApp.java
+++ app/controllers/IssueApp.java
@@ -44,6 +44,7 @@
 
     @AnonymousCheck(requiresLogin = false, displaysFlashMessage = true)
     public static Result organizationIssues(@Nonnull String organizationName, @Nonnull String state, @Nonnull String format, int pageNum) throws WriteException, IOException {
+
         // SearchCondition from param
         Form<models.support.SearchCondition> issueParamForm = new Form<>(models.support.SearchCondition.class);
         models.support.SearchCondition searchCondition = issueParamForm.bindFromRequest().get();
app/controllers/OrganizationApp.java
--- app/controllers/OrganizationApp.java
+++ app/controllers/OrganizationApp.java
@@ -9,6 +9,8 @@
 import com.avaje.ebean.Page;
 import controllers.annotation.AnonymousCheck;
 import controllers.annotation.GuestProhibit;
+import controllers.PullRequestApp.SearchCondition;
+import controllers.PullRequestApp.Category;
 import models.*;
 import models.enumeration.Operation;
 import models.enumeration.RequestState;
@@ -30,6 +32,7 @@
 import views.html.organization.members;
 import views.html.organization.setting;
 import views.html.organization.view;
+import views.html.organization.group_pullrequest_list;
 
 import javax.servlet.ServletException;
 import javax.validation.ConstraintViolation;
@@ -45,6 +48,26 @@
  */
 @AnonymousCheck
 public class OrganizationApp extends Controller {
+
+    @AnonymousCheck(requiresLogin = false, displaysFlashMessage = true)
+    public static Result organizationPullRequests(String organizationName, String category) {
+
+        Organization organization = Organization.findByName(organizationName);
+        if (organization == null) {
+            return notFound(ErrorViews.NotFound.render("error.notfound.organization"));
+        }
+
+        SearchCondition condition = Form.form(SearchCondition.class).bindFromRequest().get();
+        if (category == "open")
+            condition.setOrganization(organization).setCategory(Category.OPEN);
+        else
+            condition.setOrganization(organization).setCategory(Category.CLOSED);
+        Page<PullRequest> page = PullRequest.findPagingList(condition);
+
+        return ok(group_pullrequest_list.render("title.pullrequest",  organization, page, condition, category));
+    }
+
+
     /**
      * show New Group page
      * @return {@link Result}
app/controllers/PullRequestApp.java
--- app/controllers/PullRequestApp.java
+++ app/controllers/PullRequestApp.java
@@ -343,7 +343,7 @@
 
         // Only members can access code?
         if(project.isCodeAccessibleMemberOnly && !project.hasMember(UserApp.currentUser())) {
-                return forbidden(ErrorViews.Forbidden.render("error.forbidden", project));
+            return forbidden(ErrorViews.Forbidden.render("error.forbidden", project));
         }
 
         SearchCondition condition = Form.form(SearchCondition.class).bindFromRequest().get();
@@ -697,6 +697,12 @@
         public Long contributorId;
         public int pageNum = Constants.DEFAULT_PAGE;
         public Category category;
+        public Organization organization;
+
+        public SearchCondition setOrganization(Organization organization) {
+            this.organization = organization;
+            return this;
+        }
 
         public SearchCondition setProject(Project project) {
             this.project = project;
@@ -731,6 +737,7 @@
             clone.contributorId = this.contributorId;
             clone.pageNum = this.pageNum;
             clone.category = this.category;
+            clone.organization = this.organization;
             return clone;
         }
 
app/models/PullRequest.java
--- app/models/PullRequest.java
+++ app/models/PullRequest.java
@@ -779,6 +779,15 @@
         if (condition.project != null) {
             el.eq(condition.category.project(), condition.project);
         }
+        if (condition.organization != null) {
+            List<Project> projects = condition.organization.getVisibleProjects(UserApp.currentUser());
+            List<String> projectsIds = new ArrayList<>();
+            for (Project project : projects) {
+                projectsIds.add(project.id.toString());
+            }
+            el.in("to_project_id", projectsIds);
+            el.in("from_project_id", projectsIds);
+        }
         Expression state = createStateSearchExpression(condition.category.states());
         if (state != null) {
             el.add(state);
 
app/views/organization/group_pullrequest_list.scala.html (added)
+++ app/views/organization/group_pullrequest_list.scala.html
@@ -0,0 +1,76 @@
+@**
+* Yona, 21st Century Project Hosting SW
+*
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
+**@
+@(title: String, organization: Organization, currentPage: com.avaje.ebean.Page[PullRequest],
+        condition: controllers.PullRequestApp.SearchCondition, requestType: String)
+
+@import utils.AccessControl
+@import controllers.PullRequestApp.Category
+@import models.PushedBranch
+
+@conditionForOpen = @{condition.clone.setCategory(Category.OPEN)}
+@conditionForClosed = @{condition.clone.setCategory(Category.CLOSED)}
+
+@searchFormAction(category: Category) = @{
+    category match {
+        case Category.CLOSED => {
+            routes.OrganizationApp.organizationPullRequests(organization.name, "closed")
+        }
+        case Category.OPEN => {
+            routes.OrganizationApp.organizationPullRequests(organization.name, "open")
+        }
+    }
+}
+
+@organizationLayout(organization.name, utils.MenuType.NONE, organization) {
+    @header(organization)
+    @menu(organization)
+    <div class="page-wrap-outer">
+        <div class="project-page-wrap">
+            <div pjax-container class="row-fluid cb">
+                <div class="left-menu span2 search-wrap hide-in-mobile" style="padding-top:0;">
+                    <form id="search" name="search" action="@searchFormAction(condition.category)" method="get">
+                        <div class="search">
+                            <div class="search-bar">
+                                <input name="filter" class="textbox full" type="text" value="@condition.filter">
+                                <button type="submit" class="search-btn"><i class="yobicon-search"></i></button>
+                            </div>
+                        </div>
+                    </form>
+                </div>
+                <div class="span10 span-hard-wrap" id="span10">
+                    <ul class="nav nav-tabs nm pullrequeset-tab-menu">
+                        <li @if(requestType.equals("open")){class="active"}>
+                            <a href="#" data-url="@searchFormAction(Category.OPEN)" data-type="state">
+                                @Messages("pullRequest.state.open")
+                                <span class="num-badge">@PullRequest.count(conditionForOpen)</span>
+                            </a>
+                        </li>
+                        <li @if(requestType.equals("closed")){class="active"}>
+                            <a href="#" data-url="@searchFormAction(Category.CLOSED)" data-type="state">
+                                @Messages("pullRequest.state.closed")
+                                <span class="num-badge">@PullRequest.count(conditionForClosed)</span>
+                            </a>
+                        </li>
+                    </ul>
+                    <div class="tab-content" style="clear:both;padding-top:15px;">
+                        <div id="list" class="row-fluid tab-pane active">
+                        @views.html.organization.group_pullrequest_list_partial(organization, currentPage)
+                        </div>
+                    </div>
+                </div>
+            </div>
+            <script type="text/javascript">
+                $(function(){
+                    $('.pullrequeset-tab-menu').on('click','[data-type="state"]',function() {
+                        $('#search').attr('action', $(this).data('url'));
+                        $('#search').submit();
+                    });
+                });
+            </script>
+        </div>
+    </div>
+}
 
app/views/organization/group_pullrequest_list_partial.scala.html (added)
+++ app/views/organization/group_pullrequest_list_partial.scala.html
@@ -0,0 +1,101 @@
+@**
+* Yona, 21st Century Project Hosting SW
+*
+* Copyright Yona & Yobi Authors & NAVER Corp.
+* https://yona.io
+**@
+@(organization:Organization, page: com.avaje.ebean.Page[PullRequest])
+
+@import utils.JodaDateUtil
+@import utils.TemplateHelper._
+@import org.apache.commons.lang3.StringUtils
+
+<ul class="post-list-wrap">
+@if(page.getList.size> 0){
+  @for(req <- page.getList.iterator) {
+    @defining(User.findByLoginId(req.contributor.loginId)){ user =>
+      <li class="post-item title" href="@routes.PullRequestApp.pullRequest(req.toProject.owner, req.toProject.name, req.number)">
+        <div class="span10 span-hard-wrap">
+          <a href="@routes.UserApp.userInfo(user.loginId)" class="avatar-wrap mlarge" data-toggle="tooltip" data-placement="top" title="@user.loginId">
+            @if(user.avatarUrl == UserApp.DEFAULT_AVATAR_URL) {
+              <img src="@urlToPicture(user.email, 32)">
+            } else {
+              <img src="@user.avatarUrl" alt="@user.name" width="32" height="32"/>
+            }
+          </a>
+          <div class="title-wrap">
+            <span class="post-id">@req.number</span>
+            @showHeaderWordsInBracketsIfExist(req.title)
+            <a href="@routes.PullRequestApp.pullRequest(req.toProject.owner, req.toProject.name, req.number)" class="title @if(req.isConflict == true) {conflict}">
+              @removeHeaderWords(req.title)
+            </a>
+          </div>
+          <div class="infos">
+            @if(user.name){
+              <a href="@routes.UserApp.userInfo(user.loginId)" class="infos-item infos-link-item" data-toggle="tooltip" data-placement="top" title="@user.loginId">
+                @user.name
+              </a>
+            } else {
+              <span class="infos-item">@Messages("issue.noAuthor")</span>
+            }
+            <span class="infos-item" title="@JodaDateUtil.getDateString(req.created)">
+              @agoOrDateString(req.created)
+            </span>
+            <a href="@routes.ProjectApp.project(req.toProject.owner,req.toProject.name)" class="infos-link-item group-project-name">
+              @req.toProject.name
+            </a>
+
+            @if(!req.commentThreads.isEmpty){
+              @defining(req.countCommentThreadsByState(CommentThread.ThreadState.CLOSED)){ countClosed =>
+                <div class="infos-item" style="margin-right:20px;">
+                  <i class="infos-icon yobicon-post2 vmiddle"></i>
+                  <div class="upload-progress">
+                    <div class="bar orange" style="width: @getPercent(countClosed.toDouble, req.commentThreads.size.toDouble)%;"></div>
+                  </div>
+                  <a href="@routes.PullRequestApp.pullRequestChanges(req.toProject.owner, req.toProject.name, req.number)"
+                  data-toggle="tooltip" title="@Messages("pullRequest.review.closed") / @Messages("pullRequest.review.total")">
+                    <span>@countClosed</span>
+                    <span class="gray-txt">/</span>
+                    <span class="size total">@req.commentThreads.size</span>
+                  </a>
+                </div>
+              }
+            }
+          </div>
+        </div>
+        <div class="span2 hide-in-mobile">
+          <div class="mt5 pull-right hide-in-mobile">
+          @if(req.receiver != null) {
+            <a href="@routes.UserApp.userInfo(req.receiver.loginId)" class="avatar-wrap assinee" data-toggle="tooltip" data-placement="top" title="" data-original-title="@req.receiver.name">
+              <img src="@req.receiver.avatarUrl" width="32" height="32" alt="@req.receiver.name">
+            </a>
+          } else {
+            <div class="empty-avatar-wrap">&nbsp;</div>
+          }
+          </div>
+          <div class="state @if(req.isConflict == true) {conflict} else { @req.state.toString.toLowerCase} pull-right">@if(req.isConflict == true) {@Messages("pullRequest.state.conflict")} else {@Messages("pullRequest.state." + req.state.toString.toLowerCase)}</div>
+        </div>
+      </li>
+    }
+  }
+  <div id="pagination"></div>
+} else {
+  <div class="error-wrap">
+    <i class="ico ico-err1"></i>
+    <p>@Messages("pullRequest.is.empty")</p>
+  </div>
+}
+</ul>
+
+<script type="text/javascript">
+  $(function() {
+    yobi.Pagination.update($("#pagination"), @page.getTotalPageCount);
+    (function _initImplicitTitlePrefix() {
+      $(".title-prefix").on("click", function() {
+        var filterInput = $("input[name*='filter']");
+        filterInput.val($(this).text());
+        filterInput.closest("form").submit();
+      });
+    })();
+} );
+</script>
app/views/organization/menu.scala.html
--- app/views/organization/menu.scala.html
+++ app/views/organization/menu.scala.html
@@ -38,6 +38,11 @@
                 @Messages("menu.board")
                 </a>
             </li>
+            <li class="@if(request.path.contains(org.name + "/pullrequests")){active}">
+                <a href="@routes.OrganizationApp.organizationPullRequests(org.name)">
+                @Messages("menu.pullRequest")
+                </a>
+            </li>
         </ul>
         <div class="project-setting">
             <ul class="project-menu-nav">
conf/messages
--- conf/messages
+++ conf/messages
@@ -930,6 +930,7 @@
 title.projectSetting = Project settings
 title.projectTransfer = Project Transfer
 title.projectWatchers = Watcher list
+title.pullrequest = Pull Request
 title.recently.visited =  Recently visited
 title.rememberMe = Stay logged in
 title.resetPassword = Reset password
conf/routes
--- conf/routes
+++ conf/routes
@@ -76,6 +76,7 @@
 GET            /organizations/:organizationName                                       controllers.OrganizationApp.organization(organizationName: String)
 GET            /organizations/:organizationName/issues                                controllers.IssueApp.organizationIssues(organizationName: String, state:String ?= "", format:String ?= "html", pageNum: Int ?= 1)
 GET            /organizations/:organizationName/boards                                controllers.BoardApp.organizationBoards(organizationName: String, pageNum: Int ?= 1)
+GET            /organizations/:organizationName/pullrequests                          controllers.OrganizationApp.organizationPullRequests(organizationName: String, category: String ?= "open")
 GET            /organizations/:organizationName/settingform                           controllers.OrganizationApp.settingForm(organizationName: String)
 GET            /organizations/:organizationName/deleteForm                            controllers.OrganizationApp.deleteForm(organizationName: String)
 DELETE         /organizations/:organizationName                                       controllers.OrganizationApp.deleteOrganization(organizationName: String)
Add a comment
List