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 @@ $(() => { ...@@ -51,7 +51,8 @@ $(() => {
issueLinkBase: $boardApp.dataset.issueLinkBase, issueLinkBase: $boardApp.dataset.issueLinkBase,
rootPath: $boardApp.dataset.rootPath, rootPath: $boardApp.dataset.rootPath,
bulkUpdatePath: $boardApp.dataset.bulkUpdatePath, bulkUpdatePath: $boardApp.dataset.bulkUpdatePath,
detailIssue: Store.detail detailIssue: Store.detail,
milestoneTitle: $boardApp.dataset.boardMilestoneTitle,
}, },
computed: { computed: {
detailIssueVisible () { detailIssueVisible () {
...@@ -59,6 +60,10 @@ $(() => { ...@@ -59,6 +60,10 @@ $(() => {
}, },
}, },
created () { created () {
if (this.milestoneTitle) {
this.state.filters.milestone_title = this.milestoneTitle;
}
gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId); gl.boardService = new BoardService(this.endpoint, this.bulkUpdatePath, this.boardId);
}, },
mounted () { mounted () {
...@@ -84,7 +89,8 @@ $(() => { ...@@ -84,7 +89,8 @@ $(() => {
gl.IssueBoardsSearch = new Vue({ gl.IssueBoardsSearch = new Vue({
el: document.getElementById('js-boards-search'), el: document.getElementById('js-boards-search'),
data: { data: {
filters: Store.state.filters filters: Store.state.filters,
milestoneTitle: $boardApp.dataset.boardMilestoneTitle,
}, },
mounted () { mounted () {
gl.issueBoards.newListDropdownInit(); gl.issueBoards.newListDropdownInit();
......
...@@ -7,6 +7,12 @@ ...@@ -7,6 +7,12 @@
const Store = gl.issueBoards.BoardsStore; const Store = gl.issueBoards.BoardsStore;
gl.issueBoards.BoardSelectorForm = Vue.extend({ gl.issueBoards.BoardSelectorForm = Vue.extend({
props: {
milestonePath: {
type: String,
required: true,
},
},
data() { data() {
return { return {
board: { board: {
...@@ -15,6 +21,8 @@ ...@@ -15,6 +21,8 @@
}, },
currentBoard: Store.state.currentBoard, currentBoard: Store.state.currentBoard,
currentPage: Store.state.currentPage, currentPage: Store.state.currentPage,
milestones: [],
milestoneDropdownOpen: false,
}; };
}, },
mounted() { mounted() {
...@@ -30,13 +38,39 @@ ...@@ -30,13 +38,39 @@
return 'Save'; return 'Save';
}, },
milestoneToggleText() {
if (this.board.milestone_id) {
return this.board.milestone.title;
}
return 'Milestone';
},
}, },
methods: { methods: {
loadMilestones() {
this.milestoneDropdownOpen = !this.milestoneDropdownOpen;
if (!this.milestones.length) {
this.$http.get(this.milestonePath)
.then((res) => {
this.milestones = res.json();
});
}
},
submit() { submit() {
gl.boardService.createBoard(this.board) gl.boardService.createBoard(this.board)
.then(() => { .then(() => {
if (this.currentBoard && this.currentPage === 'edit') { if (this.currentBoard && this.currentPage === 'edit') {
this.currentBoard.name = this.board.name; 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 // Enable the button thanks to our jQuery disabling it
...@@ -50,6 +84,13 @@ ...@@ -50,6 +84,13 @@
cancel() { cancel() {
Store.state.currentPage = ''; 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'); ...@@ -15,8 +15,14 @@ require('./board_new_form');
'board-selector-form': gl.issueBoards.BoardSelectorForm, 'board-selector-form': gl.issueBoards.BoardSelectorForm,
}, },
props: { props: {
currentBoard: Object, currentBoard: {
endpoint: String, type: Object,
required: true,
},
milestonePath: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
@media (min-width: $screen-sm-min) { @media (min-width: $screen-sm-min) {
.issues-filters, .issues-filters,
.issues_bulk_update { .issues_bulk_update {
.dropdown-menu-toggle { .dropdown-menu-toggle:not(.wide) {
width: 132px; width: 132px;
} }
} }
......
...@@ -75,7 +75,7 @@ class Projects::BoardsController < Projects::ApplicationController ...@@ -75,7 +75,7 @@ class Projects::BoardsController < Projects::ApplicationController
end end
def board_params def board_params
params.require(:board).permit(:name) params.require(:board).permit(:name, :milestone_id)
end end
def find_board def find_board
...@@ -83,6 +83,11 @@ class Projects::BoardsController < Projects::ApplicationController ...@@ -83,6 +83,11 @@ class Projects::BoardsController < Projects::ApplicationController
end end
def serialize_as_json(resource) 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
end end
...@@ -5,10 +5,22 @@ module BoardsHelper ...@@ -5,10 +5,22 @@ module BoardsHelper
{ {
endpoint: namespace_project_boards_path(@project.namespace, @project), endpoint: namespace_project_boards_path(@project.namespace, @project),
board_id: board.id, board_id: board.id,
board_milestone_title: board.try(:milestone).try(:title),
disabled: "#{!can?(current_user, :admin_list, @project)}", disabled: "#{!can?(current_user, :admin_list, @project)}",
issue_link_base: namespace_project_issues_path(@project.namespace, @project), issue_link_base: namespace_project_issues_path(@project.namespace, @project),
root_path: root_path, root_path: root_path,
bulk_update_path: bulk_update_namespace_project_issues_path(@project.namespace, @project), bulk_update_path: bulk_update_namespace_project_issues_path(@project.namespace, @project),
} }
end end
def current_board_json
board = @board || @boards.first
board.to_json(
only: [:id, :name, :milestone_id],
include: {
milestone: { only: [:title] }
},
)
end
end end
...@@ -2,6 +2,7 @@ class Board < ActiveRecord::Base ...@@ -2,6 +2,7 @@ class Board < ActiveRecord::Base
belongs_to :project belongs_to :project
has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all has_many :lists, -> { order(:list_type, :position) }, dependent: :delete_all
belongs_to :milestone
validates :name, :project, presence: true validates :name, :project, presence: true
......
...@@ -19,6 +19,7 @@ class Milestone < ActiveRecord::Base ...@@ -19,6 +19,7 @@ class Milestone < ActiveRecord::Base
belongs_to :project belongs_to :project
has_many :issues has_many :issues
has_many :boards
has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues has_many :labels, -> { distinct.reorder('labels.title') }, through: :issues
has_many :merge_requests has_many :merge_requests
has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
......
...@@ -2,6 +2,7 @@ module Boards ...@@ -2,6 +2,7 @@ module Boards
class UpdateService < BaseService class UpdateService < BaseService
def execute(board) def execute(board)
board.update(name: params[:name]) board.update(name: params[:name])
board.update(milestone_id: params[:milestone_id])
end end
end end
end end
%boards-selector{ "inline-template" => true, %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 .dropdown
%button.dropdown-menu-toggle{ "@click" => "loadBoards", %button.dropdown-menu-toggle{ "@click" => "loadBoards",
data: { toggle: "dropdown" } } data: { toggle: "dropdown" } }
...@@ -25,6 +26,7 @@ ...@@ -25,6 +26,7 @@
= icon("spin spinner") = icon("spin spinner")
- if can?(current_user, :admin_board, @project) - if can?(current_user, :admin_board, @project)
%board-selector-form{ "inline-template" => true, %board-selector-form{ "inline-template" => true,
":milestone-path" => "milestonePath",
"v-if" => "currentPage === 'new' || currentPage === 'edit'" } "v-if" => "currentPage === 'new' || currentPage === 'edit'" }
= render "projects/boards/components/form" = render "projects/boards/components/form"
.dropdown-content.board-selector-page-two{ "v-if" => "currentPage === 'delete'" } .dropdown-content.board-selector-page-two{ "v-if" => "currentPage === 'delete'" }
......
...@@ -5,6 +5,25 @@ ...@@ -5,6 +5,25 @@
%input.form-control{ type: "text", %input.form-control{ type: "text",
id: "board-new-name", id: "board-new-name",
"v-model" => "board.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 .clearfix.prepend-top-10
%button.btn.btn-primary.pull-left{ type: "submit", %button.btn.btn-primary.pull-left{ type: "submit",
":disabled" => "board.name === ''", ":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 ...@@ -185,6 +185,7 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.string "name", default: "Development", null: false t.string "name", default: "Development", null: false
t.integer "milestone_id"
end end
add_index "boards", ["project_id"], name: "index_boards_on_project_id", using: :btree add_index "boards", ["project_id"], name: "index_boards_on_project_id", using: :btree
...@@ -865,14 +866,14 @@ ActiveRecord::Schema.define(version: 20170224075132) do ...@@ -865,14 +866,14 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.boolean "share_with_group_lock", default: false t.boolean "share_with_group_lock", default: false
t.integer "visibility_level", default: 20, null: false t.integer "visibility_level", default: 20, null: false
t.boolean "request_access_enabled", default: false, 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_status", default: "ready", null: false
t.string "ldap_sync_error" t.string "ldap_sync_error"
t.datetime "ldap_sync_last_update_at" t.datetime "ldap_sync_last_update_at"
t.datetime "ldap_sync_last_successful_update_at" t.datetime "ldap_sync_last_successful_update_at"
t.datetime "ldap_sync_last_sync_at" t.datetime "ldap_sync_last_sync_at"
t.datetime "deleted_at"
t.text "description_html"
t.boolean "lfs_enabled" t.boolean "lfs_enabled"
t.text "description_html"
t.integer "parent_id" t.integer "parent_id"
t.integer "shared_runners_minutes_limit" t.integer "shared_runners_minutes_limit"
t.integer "repository_size_limit", limit: 8 t.integer "repository_size_limit", limit: 8
...@@ -1083,6 +1084,7 @@ ActiveRecord::Schema.define(version: 20170224075132) do ...@@ -1083,6 +1084,7 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.datetime "created_at" t.datetime "created_at"
t.datetime "updated_at" t.datetime "updated_at"
t.integer "creator_id" t.integer "creator_id"
t.boolean "wall_enabled", default: true, null: false
t.integer "namespace_id" t.integer "namespace_id"
t.datetime "last_activity_at" t.datetime "last_activity_at"
t.string "import_url" t.string "import_url"
...@@ -1119,9 +1121,9 @@ ActiveRecord::Schema.define(version: 20170224075132) do ...@@ -1119,9 +1121,9 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.boolean "only_allow_merge_if_pipeline_succeeds", default: false, null: false t.boolean "only_allow_merge_if_pipeline_succeeds", default: false, null: false
t.boolean "has_external_issue_tracker" t.boolean "has_external_issue_tracker"
t.string "repository_storage", default: "default", null: false t.string "repository_storage", default: "default", null: false
t.boolean "repository_read_only"
t.boolean "request_access_enabled", default: false, null: false t.boolean "request_access_enabled", default: false, null: false
t.boolean "has_external_wiki" t.boolean "has_external_wiki"
t.boolean "repository_read_only"
t.boolean "lfs_enabled" t.boolean "lfs_enabled"
t.text "description_html" t.text "description_html"
t.boolean "only_allow_merge_if_all_discussions_are_resolved" t.boolean "only_allow_merge_if_all_discussions_are_resolved"
...@@ -1473,8 +1475,8 @@ ActiveRecord::Schema.define(version: 20170224075132) do ...@@ -1473,8 +1475,8 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.datetime "otp_grace_period_started_at" t.datetime "otp_grace_period_started_at"
t.boolean "ldap_email", default: false, null: false t.boolean "ldap_email", default: false, null: false
t.boolean "external", default: false t.boolean "external", default: false
t.string "incoming_email_token"
t.string "organization" t.string "organization"
t.string "incoming_email_token"
t.boolean "authorized_projects_populated" t.boolean "authorized_projects_populated"
t.boolean "auditor", default: false, null: false t.boolean "auditor", default: false, null: false
t.boolean "notified_of_own_activity", default: false, null: false t.boolean "notified_of_own_activity", default: false, null: false
...@@ -1521,8 +1523,8 @@ ActiveRecord::Schema.define(version: 20170224075132) do ...@@ -1521,8 +1523,8 @@ ActiveRecord::Schema.define(version: 20170224075132) do
t.boolean "note_events", default: false, null: false t.boolean "note_events", default: false, null: false
t.boolean "enable_ssl_verification", default: true t.boolean "enable_ssl_verification", default: true
t.boolean "build_events", default: false, null: false t.boolean "build_events", default: false, null: false
t.boolean "wiki_page_events", default: false, null: false
t.string "token" t.string "token"
t.boolean "wiki_page_events", default: false, null: false
t.boolean "pipeline_events", default: false, null: false t.boolean "pipeline_events", default: false, null: false
t.boolean "confidential_issues_events", default: false, null: false t.boolean "confidential_issues_events", default: false, null: false
end 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