Skip to content

Commit

Permalink
board: Add label feature to board
Browse files Browse the repository at this point in the history
  • Loading branch information
doortts committed Jan 13, 2016
1 parent 2b025b8 commit 5e7f61e
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 16 deletions.
25 changes: 25 additions & 0 deletions app/assets/stylesheets/less/_page.less
Original file line number Diff line number Diff line change
Expand Up @@ -6345,3 +6345,28 @@ div.diff-body[data-outdated="true"] tr:hover .icon-comment {
.group-board {
width: 100% !important;
}

.board-labels {
width: 400px;
display: inline-block;
dt {
display: none;
}
}

.post-list{
.search-wrap {
& form {
width: 90%;
.search-bar {
height: 22px;
width: 400px;
display: inline-block;
margin-right: 10px;
}
.select2-container {
height: 32px;
}
}
}
}
51 changes: 51 additions & 0 deletions app/controllers/BoardApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* Yobire, Project Hosting SW
*
* @author Suwon Chae
* Copyright 2016 the original author or authors.
*/

package controllers;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import models.IssueLabel;
import models.Posting;
import models.Project;
import play.db.ebean.Transactional;
import play.libs.Json;
import play.mvc.Result;

import java.util.HashSet;
import java.util.Set;

import static play.libs.Json.toJson;

public class BoardApi extends AbstractPostingApp {

@Transactional
public static Result updatePostLabel(String owner, String projectName, Long number) {
JsonNode json = request().body().asJson();
if(json == null) {
return badRequest("Expecting Json data");
}
Project project = Project.findByOwnerAndProjectName(owner, projectName);
Posting posting = Posting.findByNumber(project, number);
Set<IssueLabel> labels = new HashSet<>();

for(JsonNode node: json){
System.out.println("node: " + node);
Long labelId = Long.parseLong(node.asText());
labels.add(IssueLabel.finder.byId(labelId));
}

posting.labels = labels;
posting.save();

ObjectNode result = Json.newObject();
result.put("id", project.owner);
result.put("labels", toJson(posting.labels.size()));
return ok(result);
}

}
19 changes: 11 additions & 8 deletions app/controllers/BoardApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,21 +21,17 @@
package controllers;

import actions.NullProjectCheckAction;

import com.avaje.ebean.ExpressionList;
import com.avaje.ebean.Page;

import com.fasterxml.jackson.databind.node.ObjectNode;
import controllers.annotation.AnonymousCheck;
import controllers.annotation.IsAllowed;
import controllers.annotation.IsCreatable;
import models.*;
import models.enumeration.Operation;
import models.enumeration.ResourceType;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.databind.node.ObjectNode;

import play.data.Form;
import play.db.ebean.Transactional;
import play.libs.Json;
Expand All @@ -54,22 +50,27 @@

import javax.annotation.Nonnull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.*;

import static com.avaje.ebean.Expr.icontains;

public class BoardApp extends AbstractPostingApp {
public static class SearchCondition extends AbstractPostingApp.SearchCondition {
public List<String> projectNames;
public String [] labelIds;
public Set<Long> labelIdSet = new HashSet<>();
private ExpressionList<Posting> asExpressionList(Project project) {
ExpressionList<Posting> el = Posting.finder.where().eq("project.id", project.id);

if (filter != null) {
el.or(icontains("title", filter), icontains("body", filter));
}

if (CollectionUtils.isNotEmpty(labelIdSet)) {
Set<IssueLabel> labels = IssueLabel.finder.where().idIn(new ArrayList<>(labelIdSet)).findSet();
el.in("id", Posting.finder.where().in("labels", labels).findIds());
}

if (StringUtils.isNotBlank(orderBy)) {
el.orderBy(orderBy + " " + orderDir);
}
Expand Down Expand Up @@ -155,6 +156,8 @@ public static Result posts(String userName, String projectName, int pageNum) {
if (searchCondition.orderBy.equals("id")) {
searchCondition.orderBy = "createdDate";
}
searchCondition.labelIdSet.addAll(LabelApp.getLabelIds(request()));
searchCondition.labelIdSet.remove(null);

ExpressionList<Posting> el = searchCondition.asExpressionList(project);
el.eq("notice", false);
Expand Down
3 changes: 3 additions & 0 deletions app/models/IssueLabel.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ public IssueLabelException(String s) {
@ManyToMany(mappedBy="labels", fetch = FetchType.EAGER)
public Set<Issue> issues;

@ManyToMany(mappedBy="labels", fetch = FetchType.EAGER)
public Set<Posting> postings;

public static List<IssueLabel> findByProject(Project project) {
return finder.where()
.eq("project.id", project.id)
Expand Down
15 changes: 15 additions & 0 deletions app/models/Posting.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

import javax.persistence.*;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static com.avaje.ebean.Expr.eq;

Expand All @@ -27,6 +29,19 @@ public class Posting extends AbstractPosting {
@OneToMany(cascade = CascadeType.ALL)
public List<PostingComment> comments;

@ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.REMOVE)
public Set<IssueLabel> labels;

public Set<Long> getLabelIds() {
Set<Long> labelIds = new HashSet<>();

for(IssueLabel label : this.labels){
labelIds.add(label.id);
}

return labelIds;
}

public Posting(Project project, User author, String title, String body) {
super(project, author, title, body);
}
Expand Down
19 changes: 15 additions & 4 deletions app/views/board/list.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,30 @@

@makeFilterLink(fieldName:String, orderBy:String, orderDir:String, fieldText:String) = {
@if(orderBy.equals(fieldName)) {
<a href="@urlToList?orderBy=@fieldName&orderDir=@if(orderDir.equals("desc")){asc}else{desc}" class="filter active"><i class="ico btn-gray-arrow @if(orderDir.equals("desc")){ down }"></i>@fieldText</a>
<a href="@urlToList?[email protected]&[email protected]&orderBy=@fieldName&orderDir=@if(orderDir.equals("desc")){asc}else{desc}" class="filter active"><i class="ico btn-gray-arrow @if(orderDir.equals("desc")){ down }"></i>@fieldText</a>
} else {
<a href="@urlToList?orderBy=@fieldName&orderDir=desc" class="filter"><i class="ico btn-gray-arrow down"></i>@fieldText</a>
<a href="@urlToList?[email protected]&[email protected]&orderBy=@fieldName&orderDir=desc" class="filter"><i class="ico btn-gray-arrow down"></i>@fieldText</a>
}
}

@projectLayout(title, project, utils.MenuType.BOARD) {
@projectMenu(project, utils.MenuType.BOARD, "main-menu-only")
<link rel="stylesheet" href="@routes.IssueLabelApp.labelStyles(project.owner, project.name)" type="text/css" />
<div class="page-wrap-outer">
<div class="project-page-wrap">
<div class="post-list project-page-wrap">
<div class="search-wrap underline">
<form id="option_form" method="get" class="pull-left">
<form id="option_form" action="@routes.BoardApp.posts(project.owner, project.name)" method="get" class="pull-left">
<input type="hidden" name="orderBy" value="@param.orderBy">
<input type="hidden" name="orderDir" value="@param.orderDir">
<div class="search-bar">
<input name="filter" class="textbox" type="text" placeholder="@Messages("project.searchPlaceholder")" value="@param.filter">
<button type="submit" class="search-btn"><i class="yobicon-search"></i></button>
</div>
<div class="board-labels">
@if(!IssueLabel.findByProject(project).isEmpty){
@issue.partial_select_label(IssueLabel.findByProject(project), param.labelIdSet)
}
</div>
</form>
<div class="pull-right">
<a href="@routes.BoardApp.newPostForm(project.owner, project.name)" class="ybtn ybtn-success">@Messages("post.write")</a>
Expand Down Expand Up @@ -107,6 +113,11 @@
"N": "@routes.BoardApp.newPostForm(project.owner, project.name)"
});
}

$('.board-labels select').on('change', function(e){
$("#option_form").submit();
});
});
</script>
@common.select2()
}
5 changes: 4 additions & 1 deletion app/views/board/partial_list.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@
<span class="infos-item item-count-groups">
@countHtml("comments",routes.BoardApp.post(project.owner, project.name, post.getNumber).toString() + "#comments", post.numOfComments)
</span>
}
}
@for(label <- post.labels) {
<a href="#" class="label issue-label list-label active" data-category-id="@label.category.id" data-label-id="@label.id">@label.name</a>
}
</div>
</li>
}
28 changes: 26 additions & 2 deletions app/views/board/view.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* http://yobi.io
*
* @author Ahn Hyeok Jun
* @author Suwon Chae
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -65,9 +66,18 @@
<strong class="name">@Messages("common.noAuthor")</strong>
}
</a>
<div class="board-labels pull-right">
@if(!IssueLabel.findByProject(project).isEmpty){
@if(isAllowed(UserApp.currentUser(), post.asResource(), Operation.UPDATE)){
@issue.partial_select_label(IssueLabel.findByProject(project), post.getLabelIds)
} else {
@issue.partial_show_selected_label(post.labels.toList, "")
}
}
</div>
</div>
<div class="content markdown-wrap">@Html(Markdown.render(post.body, post.asResource().getProject()))</div>
<div class="attachments" id="attachments" data-attachments="@toJson(AttachmentApp.getFileList(ResourceType.BOARD_POST.toString(), post.id.toString()))"></div>
<div class="content markdown-wrap">@Html(Markdown.render(post.body, post.asResource().getProject()))</div>
<div class="attachments" id="attachments" data-attachments="@toJson(AttachmentApp.getFileList(ResourceType.BOARD_POST.toString(), post.id.toString()))"></div>
</div>
<div class="board-footer board-actrow">
<div class="pull-left">
Expand Down Expand Up @@ -122,6 +132,7 @@ <h3>@Messages("button.confirm")</h3>
@common.markdown(project)
@common.commentDeleteModal()

<link rel="stylesheet" type="text/css" media="screen" href="@routes.IssueLabelApp.labelStyles(project.owner, project.name)">
<link rel="stylesheet" type="text/css" media="screen" href="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.css")">
<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.caret.min.js")"></script>
<script type="text/javascript" src="@routes.Assets.at("javascripts/lib/atjs/jquery.atwho.js")"></script>
Expand All @@ -148,6 +159,19 @@ <h3>@Messages("button.confirm")</h3>
"target": "textarea[id^=editor-]",
"url" : "@Html(routes.ProjectApp.mentionList(project.owner, project.name, post.getNumber, post.asResource().getType.resource()).toString())"
});

$('.board-labels select').on('change', function(e){
var url = "@Html(routes.BoardApi.updatePostLabel(project.owner, project.name, post.getNumber).toString())";
$.ajax({
url: url,
method: "POST",
data: JSON.stringify(e.val),
contentType: "application/json; charset=UTF-8"
}).done(function(response) {
}).error(function(response){
});
});
});
</script>
@common.select2()
}
2 changes: 1 addition & 1 deletion app/views/issue/partial_select_label.scala.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<select name="labelIds" multiple="multiple" data-search="labelIds"
data-toggle="select2" data-format="issuelabel" data-allow-clear="true"
data-dropdown-css-class="issue-labels" data-container-css-class="issue-labels bordered fullsize"
data-placeholder="@Messages("label.select")" @additionalAttr>
data-placeholder="@Messages("label.select")" @additionalAttr class="hide">
<option></option>
@labels.groupBy(_.category).map {
case (category, labels) => {
Expand Down
14 changes: 14 additions & 0 deletions conf/evolutions/default/106.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# --- !Ups
create table posting_issue_label (
posting_id bigint not null,
issue_label_id bigint not null,
constraint pk_posting_issue_label primary key (posting_id, issue_label_id))
;

alter table posting_issue_label add constraint fk_posting_issue_label_issue_01 foreign key (posting_id) references posting (id) on delete restrict on update restrict;

alter table posting_issue_label add constraint fk_posting_issue_label_issue_la_02 foreign key (issue_label_id) references issue_label (id) on delete restrict on update restrict;

# --- !Downs
drop table if exists POSTING_ISSUE_LABEL;

4 changes: 4 additions & 0 deletions conf/routes
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ GET /UIKit
# Search
GET /search controllers.SearchApp.searchInAll()

GET /-_-api controllers.Application.index()
GET /-_-api/v1/ controllers.Application.index()
POST /-_-api/v1/owners/:owner/projects/:projectName/:number controllers.BoardApi.updatePostLabel(owner:String, projectName:String, number:Long)

# Import
GET /import controllers.ImportApp.importForm()
POST /import controllers.ImportApp.newProject()
Expand Down
29 changes: 29 additions & 0 deletions public/javascripts/service/yobi.board.List.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
htElement.welInputOrderBy = htElement.welForm.find("input[name=orderBy]");
htElement.welInputOrderDir = htElement.welForm.find("input[name=orderDir]");
htElement.welInputPageNum = htElement.welForm.find("input[name=pageNum]");
htElement.welIssueWrap = $(htOptions.welIssueWrap || '.post-list-wrap');

htElement.welPages = $(htOptions.sQueryPages || "#pagination a");
htElement.welPagination = $(htOptions.elPagination || '#pagination');
Expand All @@ -63,6 +64,7 @@
*/
function _attachEvent() {
htElement.welPages.click(_onClickPage);
htElement.welIssueWrap.on("click", "a[data-label-id][data-category-id]", _onClickLabelOnList);
}

/**
Expand All @@ -74,6 +76,33 @@
return false;
}

/**
* "click" event handler of labels on the list.
* Add clicked label to search form condition.
*
* @param event
* @private
*/
function _onClickLabelOnList(weEvt) {
weEvt.preventDefault();

var link = $(this);
var targetQuery = "[data-search=labelIds]";
var target = htElement.welForm.find(targetQuery);

var labelId = link.data("labelId");
var newValue;

if(target.prop("multiple")){
newValue = (target.val() || []);
newValue.push(labelId);
} else {
newValue = labelId;
}

target.data("select2").val(newValue, true); // triggerChange=true
console.log("labelId", labelId);
}

function _initPagination(htOptions){
yobi.Pagination.update(htElement.welPagination, htOptions.nTotalPages);
Expand Down

0 comments on commit 5e7f61e

Please sign in to comment.