Commit 29aec403 authored by Phil Hughes's avatar Phil Hughes Committed by Douglas Barbosa Alexandre

Create & edit a board with a milestone

Closes #1587
parent 2cc964d7
......@@ -51,7 +51,8 @@ $(() => {
issueLinkBase: $boardApp.dataset.issueLinkBase,
rootPath: $boardApp.dataset.rootPath,
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
detailIssue: Store.detail
detailIssue: Store.detail,
milestoneTitle: $boardApp.dataset.boardMilestoneTitle,
},
computed: {
detailIssueVisible () {
......@@ -59,6 +60,10 @@ $(() => {
},
},
created () {
if (this.milestoneTitle) {
this.state.filters.milestone_title = this.milestoneTitle;
}
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
},
mounted () {
......@@ -84,7 +89,8 @@ $(() => {
gl.IssueBoardsSearch = new Vue({
el: document.getElementById('js-boards-search'),
data: {
filters: Store.state.filters
filters: Store.state.filters,
milestoneTitle: $boardApp.dataset.boardMilestoneTitle,
},
mounted () {
gl.issueBoards.newListDropdownInit();
......
......@@ -7,6 +7,12 @@
const Store = gl.issueBoards.BoardsStore;
gl.issueBoards.BoardSelectorForm = Vue.extend({
props: {
milestonePath: {
type: String,
required: true,
},
},
data() {
return {
board: {
......@@ -15,6 +21,8 @@
},
currentBoard: Store.state.currentBoard,
currentPage: Store.state.currentPage,
milestones: [],
milestoneDropdownOpen: false,
};
},
mounted() {
......@@ -30,13 +38,39 @@
return 'Save';
},
milestoneToggleText() {
if (this.board.milestone_id) {
return this.board.milestone.title;
}
return 'Milestone';
},
},
methods: {
loadMilestones() {
this.milestoneDropdownOpen = !this.milestoneDropdownOpen;
if (!this.milestones.length) {
this.$http.get(this.milestonePath)
.then((res) => {
this.milestones = res.json();
});
}
},
submit() {
gl.boardService.createBoard(this.board)
.then(() => {
if (this.currentBoard && this.currentPage === 'edit') {
this.currentBoard.name = this.board.name;
if (this.board.milestone_id) {
this.currentBoard.milestone_id = this.board.milestone_id;
this.currentBoard.milestone = {
title: this.board.milestone.title,
},
Store.state.filters.milestone_title = this.currentBoard.milestone.title;
}
}
// Enable the button thanks to our jQuery disabling it
......@@ -50,6 +84,13 @@
cancel() {
Store.state.currentPage = '';
},
selectMilestone(milestone) {
this.milestoneDropdownOpen = false;
this.board.milestone_id = milestone.id;
this.board.milestone = {
title: milestone.title,
};
},
},
});
})();
......@@ -15,8 +15,14 @@ require('./board_new_form');
'board-selector-form': gl.issueBoards.BoardSelectorForm,
},
props: {
currentBoard: Object,
endpoint: String,
currentBoard: {
type: Object,
required: true,
},
milestonePath: {
type: String,
required: true,
},
},
data() {
return {
......
......@@ -10,7 +10,7 @@
@media (min-width: $screen-sm-min) {
.issues-filters,
.issues_bulk_update {
.dropdown-menu-toggle {
.dropdown-menu-toggle:not(.wide) {
width: 132px;
}
}
......
......@@ -75,7 +75,7 @@ class Projects::BoardsController < Projects::ApplicationController
end
def board_params
params.require(:board).permit(:name)
params.require(:board).permit(:name, :milestone_id)
end
def find_board
......@@ -83,6 +83,11 @@ class Projects::BoardsController < Projects::ApplicationController
end
def serialize_as_json(resource)
resource.as_json(only: [:id, :name])
resource.as_json(
only: [:id, :name, :milestone_id],
include: {
milestone: { only: [:title] }
},
)
end
end
......@@ -5,10 +5,22 @@ module BoardsHelper
{
endpoint: namespace_project_boards_path(@project.namespace, @project),
board_id: board.id,
board_milestone_title: board.try(:milestone).try(:title),
disabled: "#{!can?(current_user, :admin_list, @project)}",
issue_link_base: namespace_project_issues_path(@project.namespace, @project),
root_path: root_path,
bulk_update_path: bulk_update_namespace_project_issues_path(@project.namespace, @project),
}
end
def current_board_json
board = @board || @boards.first
board.to_json(
only: [:id, :name, :milestone_id],
include: {
milestone: { only: [:title] }
},
)
end
end
......@@ -2,6 +2,7 @@ class Board < ActiveRecord::Base
belongs_to :project
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all
belongs_to :milestone
validates :name, :project, presence: true
......
......@@ -19,6 +19,7 @@ class Milestone < ActiveRecord::Base
belongs_to :project
has_many :issues
has_many :boards
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests
has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
......
......@@ -2,6 +2,7 @@ module Boards
class UpdateService < BaseService
def execute(board)
board.update(name: params[:name])
board.update(milestone_id: params[:milestone_id])
end
end
end
%boards-selector{ "inline-template" => true,
":current-board" => board.to_json }
":current-board" => current_board_json,
"milestone-path" => namespace_project_milestones_path(board.project.namespace, board.project, :json) }
.dropdown
%button.dropdown-menu-toggle{ "@click" => "loadBoards",
data: { toggle: "dropdown" } }
......@@ -25,6 +26,7 @@
= icon("spin spinner")
- if can?(current_user, :admin_board, @project)
%board-selector-form{ "inline-template" => true,
":milestone-path" => "milestonePath",
"v-if" => "currentPage === 'new' || currentPage === 'edit'" }
= render "projects/boards/components/form"
.dropdown-content.board-selector-page-two{ "v-if" => "currentPage === 'delete'" }
......
......@@ -5,6 +5,25 @@
%input.form-control{ type: "text",
id: "board-new-name",
"v-model" => "board.name" }
%label.label-light{ for: "board-milestone" }
Board milestone
%input{ type: "hidden",
id: "board-milestone",
"v-model.number" => "board.milestone_id" }
.dropdown{ ":class" => "{ open: milestoneDropdownOpen }" }
%button.dropdown-menu-toggle.wide{ type: "button",
"@click.stop.prevent" => "loadMilestones" }
{{ milestoneToggleText }}
= icon("chevron-down")
.dropdown-menu.dropdown-menu-selectable{ "v-if" => "milestoneDropdownOpen" }
.dropdown-content
%ul
%li{ "v-for" => "milestone in milestones" }
%a{ href: "#",
":class" => "{ 'is-active': milestone.id === board.milestone_id }",
"@click.stop.prevent" => "selectMilestone(milestone)" }
{{ milestone.title }}
= dropdown_loading
.clearfix.prepend-top-10
%button.btn.btn-primary.pull-left{ type: "submit",
":disabled" => "board.name === ''",
......
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddMilestoneIdToBoards < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME = false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def up
add_column :boards, :milestone_id, :integer, null: true
end
def down
remove_column :boards, :milestone_id
end
end
......@@ -185,6 +185,7 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "name", default: "Development", null: false
t.integer "milestone_id"
end
add_index "boards", ["project_id"], name: "index_boards_on_project_id", using: :btree
......@@ -865,14 +866,14 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.boolean "share_with_group_lock", default: false
t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: false, null: false
t.datetime "deleted_at"
t.string "ldap_sync_status", default: "ready", null: false
t.string "ldap_sync_error"
t.datetime "ldap_sync_last_update_at"
t.datetime "ldap_sync_last_successful_update_at"
t.datetime "ldap_sync_last_sync_at"
t.datetime "deleted_at"
t.text "description_html"
t.boolean "lfs_enabled"
t.text "description_html"
t.integer "parent_id"
t.integer "shared_runners_minutes_limit"
t.integer "repository_size_limit", limit: 8
......@@ -1083,6 +1084,7 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "creator_id"
t.boolean "wall_enabled", default: true, null: false
t.integer "namespace_id"
t.datetime "last_activity_at"
t.string "import_url"
......@@ -1119,9 +1121,9 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.boolean "only_allow_merge_if_pipeline_succeeds", default: false, null: false
t.boolean "has_external_issue_tracker"
t.string "repository_storage", default: "default", null: false
t.boolean "repository_read_only"
t.boolean "request_access_enabled", default: false, null: false
t.boolean "has_external_wiki"
t.boolean "repository_read_only"
t.boolean "lfs_enabled"
t.text "description_html"
t.boolean "only_allow_merge_if_all_discussions_are_resolved"
......@@ -1473,8 +1475,8 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false
t.boolean "external", default: false
t.string "incoming_email_token"
t.string "organization"
t.string "incoming_email_token"
t.boolean "authorized_projects_populated"
t.boolean "auditor", default: false, null: false
t.boolean "notified_of_own_activity", default: false, null: false
......@@ -1521,8 +1523,8 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.boolean "note_events", default: false, null: false
t.boolean "enable_ssl_verification", default: true
t.boolean "build_events", default: false, null: false
t.boolean "wiki_page_events", default: false, null: false
t.string "token"
t.boolean "wiki_page_events", default: false, null: false
t.boolean "pipeline_events", default: false, null: false
t.boolean "confidential_issues_events", default: false, null: false
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment