Merge branch 'issue-boards-new-issue' into 'master'
Issue boards new issue form ## What does this MR do? Adds a new issue form into the issue boards lists. ## Screenshots (if relevant) ![Screen_Shot_2016-10-03_at_14.57.30](/uploads/17fe6cd37bd020a2ee1688e0b496c18f/Screen_Shot_2016-10-03_at_14.57.30.png) ![Screen_Shot_2016-10-03_at_14.57.32](/uploads/c3f12bcb9ff9a0e7ce5b0bb06dfb0dd7/Screen_Shot_2016-10-03_at_14.57.32.png) ## What are the relevant issue numbers? Part of #21219 See merge request !6653
This commit is contained in:
commit
9a13f885a9
|
@ -23,6 +23,7 @@ v 8.13.0 (unreleased)
|
||||||
- Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell)
|
- Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell)
|
||||||
- Add Issue Board API support (andrebsguedes)
|
- Add Issue Board API support (andrebsguedes)
|
||||||
- Allow the Koding integration to be configured through the API
|
- Allow the Koding integration to be configured through the API
|
||||||
|
- Add new issue button to each list on Issues Board
|
||||||
- Added soft wrap button to repository file/blob editor
|
- Added soft wrap button to repository file/blob editor
|
||||||
- Add word-wrap to issue title on issue and milestone boards (ClemMakesApps)
|
- Add word-wrap to issue title on issue and milestone boards (ClemMakesApps)
|
||||||
- Fix todos page mobile viewport layout (ClemMakesApps)
|
- Fix todos page mobile viewport layout (ClemMakesApps)
|
||||||
|
|
|
@ -21,7 +21,8 @@
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
filters: Store.state.filters
|
filters: Store.state.filters,
|
||||||
|
showIssueForm: false
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -33,6 +34,11 @@
|
||||||
deep: true
|
deep: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
showNewIssueForm() {
|
||||||
|
this.showIssueForm = !this.showIssueForm;
|
||||||
|
}
|
||||||
|
},
|
||||||
ready () {
|
ready () {
|
||||||
const options = gl.issueBoards.getBoardSortableDefaultOptions({
|
const options = gl.issueBoards.getBoardSortableDefaultOptions({
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
//= require ./board_card
|
//= require ./board_card
|
||||||
|
//= require ./board_new_issue
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
const Store = gl.issueBoards.BoardsStore;
|
const Store = gl.issueBoards.BoardsStore;
|
||||||
|
@ -8,14 +9,16 @@
|
||||||
|
|
||||||
gl.issueBoards.BoardList = Vue.extend({
|
gl.issueBoards.BoardList = Vue.extend({
|
||||||
components: {
|
components: {
|
||||||
'board-card': gl.issueBoards.BoardCard
|
'board-card': gl.issueBoards.BoardCard,
|
||||||
|
'board-new-issue': gl.issueBoards.BoardNewIssue
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
list: Object,
|
list: Object,
|
||||||
issues: Array,
|
issues: Array,
|
||||||
loading: Boolean,
|
loading: Boolean,
|
||||||
issueLinkBase: String
|
issueLinkBase: String,
|
||||||
|
showIssueForm: Boolean
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
@ -73,7 +76,7 @@
|
||||||
group: 'issues',
|
group: 'issues',
|
||||||
sort: false,
|
sort: false,
|
||||||
disabled: this.disabled,
|
disabled: this.disabled,
|
||||||
filter: '.board-list-count',
|
filter: '.board-list-count, .is-disabled',
|
||||||
onStart: (e) => {
|
onStart: (e) => {
|
||||||
const card = this.$refs.issue[e.oldIndex];
|
const card = this.$refs.issue[e.oldIndex];
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
(() => {
|
||||||
|
window.gl = window.gl || {};
|
||||||
|
|
||||||
|
gl.issueBoards.BoardNewIssue = Vue.extend({
|
||||||
|
props: {
|
||||||
|
list: Object,
|
||||||
|
showIssueForm: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
title: '',
|
||||||
|
error: false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
showIssueForm () {
|
||||||
|
this.$els.input.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
submit(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (this.title.trim() === '') return;
|
||||||
|
|
||||||
|
this.error = false;
|
||||||
|
|
||||||
|
const labels = this.list.label ? [this.list.label] : [];
|
||||||
|
const issue = new ListIssue({
|
||||||
|
title: this.title,
|
||||||
|
labels
|
||||||
|
});
|
||||||
|
|
||||||
|
this.list.newIssue(issue)
|
||||||
|
.then((data) => {
|
||||||
|
// Need this because our jQuery very kindly disables buttons on ALL form submissions
|
||||||
|
$(this.$els.submitButton).enable();
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
// Need this because our jQuery very kindly disables buttons on ALL form submissions
|
||||||
|
$(this.$els.submitButton).enable();
|
||||||
|
|
||||||
|
// Remove the issue
|
||||||
|
this.list.removeIssue(issue);
|
||||||
|
|
||||||
|
// Show error message
|
||||||
|
this.error = true;
|
||||||
|
this.showIssueForm = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.cancel();
|
||||||
|
},
|
||||||
|
cancel() {
|
||||||
|
this.showIssueForm = false;
|
||||||
|
this.title = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
|
@ -21,7 +21,7 @@
|
||||||
fallbackClass: 'is-dragging',
|
fallbackClass: 'is-dragging',
|
||||||
fallbackOnBody: true,
|
fallbackOnBody: true,
|
||||||
ghostClass: 'is-ghost',
|
ghostClass: 'is-ghost',
|
||||||
filter: '.has-tooltip',
|
filter: '.has-tooltip, .btn',
|
||||||
delay: gl.issueBoards.touchEnabled ? 100 : 0,
|
delay: gl.issueBoards.touchEnabled ? 100 : 0,
|
||||||
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
|
scrollSensitivity: gl.issueBoards.touchEnabled ? 60 : 100,
|
||||||
scrollSpeed: 20,
|
scrollSpeed: 20,
|
||||||
|
|
|
@ -87,6 +87,17 @@ class List {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newIssue (issue) {
|
||||||
|
this.addIssue(issue);
|
||||||
|
this.issuesSize++;
|
||||||
|
|
||||||
|
return gl.boardService.newIssue(this.id, issue)
|
||||||
|
.then((resp) => {
|
||||||
|
const data = resp.json();
|
||||||
|
issue.id = data.iid;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
createIssues (data) {
|
createIssues (data) {
|
||||||
data.forEach((issueObj) => {
|
data.forEach((issueObj) => {
|
||||||
this.addIssue(new ListIssue(issueObj));
|
this.addIssue(new ListIssue(issueObj));
|
||||||
|
|
|
@ -58,4 +58,10 @@ class BoardService {
|
||||||
to_list_id
|
to_list_id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
newIssue (id, issue) {
|
||||||
|
return this.issues.save({ id }, {
|
||||||
|
issue
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -162,6 +162,10 @@ lex
|
||||||
list-style: none;
|
list-style: none;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
&.is-smaller {
|
||||||
|
height: calc(100% - 185px);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.board-list-loading {
|
.board-list-loading {
|
||||||
|
@ -233,3 +237,31 @@ lex
|
||||||
margin-right: 5px;
|
margin-right: 5px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.board-new-issue-form {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-issue-count-holder {
|
||||||
|
margin-top: -3px;
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
line-height: 12px;
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.board-issue-count {
|
||||||
|
padding-right: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
line-height: 21px;
|
||||||
|
border-radius: $border-radius-base;
|
||||||
|
border: 1px solid $border-color;
|
||||||
|
|
||||||
|
&.has-btn {
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-width: 1px 0 1px 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ module Projects
|
||||||
module Boards
|
module Boards
|
||||||
class IssuesController < Boards::ApplicationController
|
class IssuesController < Boards::ApplicationController
|
||||||
before_action :authorize_read_issue!, only: [:index]
|
before_action :authorize_read_issue!, only: [:index]
|
||||||
|
before_action :authorize_create_issue!, only: [:create]
|
||||||
before_action :authorize_update_issue!, only: [:update]
|
before_action :authorize_update_issue!, only: [:update]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
|
@ -9,16 +10,23 @@ module Projects
|
||||||
issues = issues.page(params[:page])
|
issues = issues.page(params[:page])
|
||||||
|
|
||||||
render json: {
|
render json: {
|
||||||
issues: issues.as_json(
|
issues: serialize_as_json(issues),
|
||||||
only: [:iid, :title, :confidential],
|
|
||||||
include: {
|
|
||||||
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
|
|
||||||
labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
|
|
||||||
}),
|
|
||||||
size: issues.total_count
|
size: issues.total_count
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
list = project.board.lists.find(params[:list_id])
|
||||||
|
service = ::Boards::Issues::CreateService.new(project, current_user, issue_params)
|
||||||
|
issue = service.execute(list)
|
||||||
|
|
||||||
|
if issue.valid?
|
||||||
|
render json: serialize_as_json(issue)
|
||||||
|
else
|
||||||
|
render json: issue.errors, status: :unprocessable_entity
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update
|
def update
|
||||||
service = ::Boards::Issues::MoveService.new(project, current_user, move_params)
|
service = ::Boards::Issues::MoveService.new(project, current_user, move_params)
|
||||||
|
|
||||||
|
@ -43,6 +51,10 @@ module Projects
|
||||||
return render_403 unless can?(current_user, :read_issue, project)
|
return render_403 unless can?(current_user, :read_issue, project)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authorize_create_issue!
|
||||||
|
return render_403 unless can?(current_user, :admin_issue, project)
|
||||||
|
end
|
||||||
|
|
||||||
def authorize_update_issue!
|
def authorize_update_issue!
|
||||||
return render_403 unless can?(current_user, :update_issue, issue)
|
return render_403 unless can?(current_user, :update_issue, issue)
|
||||||
end
|
end
|
||||||
|
@ -54,6 +66,19 @@ module Projects
|
||||||
def move_params
|
def move_params
|
||||||
params.permit(:id, :from_list_id, :to_list_id)
|
params.permit(:id, :from_list_id, :to_list_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def issue_params
|
||||||
|
params.require(:issue).permit(:title).merge(request: request)
|
||||||
|
end
|
||||||
|
|
||||||
|
def serialize_as_json(resource)
|
||||||
|
resource.as_json(
|
||||||
|
only: [:iid, :title, :confidential],
|
||||||
|
include: {
|
||||||
|
assignee: { only: [:id, :name, :username], methods: [:avatar_url] },
|
||||||
|
labels: { only: [:id, :title, :description, :color, :priority], methods: [:text_color] }
|
||||||
|
})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
module Boards
|
||||||
|
module Issues
|
||||||
|
class CreateService < Boards::BaseService
|
||||||
|
def execute(list)
|
||||||
|
params.merge!(label_ids: [list.label_id])
|
||||||
|
create_issue
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def create_issue
|
||||||
|
::Issues::CreateService.new(project, current_user, params).execute
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,8 +12,17 @@
|
||||||
%header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" }
|
%header.board-header{ ":class" => "{ 'has-border': list.label }", ":style" => "{ borderTopColor: (list.label ? list.label.color : null) }" }
|
||||||
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
|
%h3.board-title.js-board-handle{ ":class" => "{ 'user-can-drag': (!disabled && !list.preset) }" }
|
||||||
{{ list.title }}
|
{{ list.title }}
|
||||||
%span.pull-right{ "v-if" => "list.type !== 'blank'" }
|
.board-issue-count-holder.pull-right.clearfix{ "v-if" => "list.type !== 'blank'" }
|
||||||
{{ list.issuesSize }}
|
%span.board-issue-count.pull-left{ ":class" => "{ 'has-btn': list.type !== 'done' }" }
|
||||||
|
{{ list.issuesSize }}
|
||||||
|
- if can?(current_user, :admin_issue, @project)
|
||||||
|
%button.btn.btn-small.btn-default.pull-right.has-tooltip{ type: "button",
|
||||||
|
"@click" => "showNewIssueForm",
|
||||||
|
"v-if" => "list.type !== 'done'",
|
||||||
|
"aria-label" => "Add an issue",
|
||||||
|
"title" => "Add an issue",
|
||||||
|
data: { placement: "top", container: "body" } }
|
||||||
|
= icon("plus")
|
||||||
- if can?(current_user, :admin_list, @project)
|
- if can?(current_user, :admin_list, @project)
|
||||||
%board-delete{ "inline-template" => true,
|
%board-delete{ "inline-template" => true,
|
||||||
":list" => "list",
|
":list" => "list",
|
||||||
|
@ -26,12 +35,38 @@
|
||||||
":issues" => "list.issues",
|
":issues" => "list.issues",
|
||||||
":loading" => "list.loading",
|
":loading" => "list.loading",
|
||||||
":disabled" => "disabled",
|
":disabled" => "disabled",
|
||||||
|
":show-issue-form.sync" => "showIssueForm",
|
||||||
":issue-link-base" => "issueLinkBase" }
|
":issue-link-base" => "issueLinkBase" }
|
||||||
.board-list-loading.text-center{ "v-if" => "loading" }
|
.board-list-loading.text-center{ "v-if" => "loading" }
|
||||||
= icon("spinner spin")
|
= icon("spinner spin")
|
||||||
|
- if can? current_user, :create_issue, @project
|
||||||
|
%board-new-issue{ "inline-template" => true,
|
||||||
|
":list" => "list",
|
||||||
|
":show-issue-form.sync" => "showIssueForm",
|
||||||
|
"v-show" => "list.type !== 'done' && showIssueForm" }
|
||||||
|
.card.board-new-issue-form
|
||||||
|
%form{ "@submit" => "submit($event)" }
|
||||||
|
.flash-container{ "v-if" => "error" }
|
||||||
|
.flash-alert
|
||||||
|
An error occured. Please try again.
|
||||||
|
%label.label-light{ ":for" => "list.id + '-title'" }
|
||||||
|
Title
|
||||||
|
%input.form-control{ type: "text",
|
||||||
|
"v-model" => "title",
|
||||||
|
"v-el:input" => true,
|
||||||
|
":id" => "list.id + '-title'" }
|
||||||
|
.clearfix.prepend-top-10
|
||||||
|
%button.btn.btn-success.pull-left{ type: "submit",
|
||||||
|
":disabled" => "title === ''",
|
||||||
|
"v-el:submit-button" => true }
|
||||||
|
Submit issue
|
||||||
|
%button.btn.btn-default.pull-right{ type: "button",
|
||||||
|
"@click" => "cancel" }
|
||||||
|
Cancel
|
||||||
%ul.board-list{ "v-el:list" => true,
|
%ul.board-list{ "v-el:list" => true,
|
||||||
"v-show" => "!loading",
|
"v-show" => "!loading",
|
||||||
":data-board" => "list.id" }
|
":data-board" => "list.id",
|
||||||
|
":class" => "{ 'is-smaller': showIssueForm }" }
|
||||||
= render "projects/boards/components/card"
|
= render "projects/boards/components/card"
|
||||||
%li.board-list-count.text-center{ "v-if" => "showCount" }
|
%li.board-list-count.text-center{ "v-if" => "showCount" }
|
||||||
= icon("spinner spin", "v-show" => "list.loadingMore" )
|
= icon("spinner spin", "v-show" => "list.loadingMore" )
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
":issue-link-base" => "issueLinkBase",
|
":issue-link-base" => "issueLinkBase",
|
||||||
":disabled" => "disabled",
|
":disabled" => "disabled",
|
||||||
"track-by" => "id" }
|
"track-by" => "id" }
|
||||||
%li.card{ ":class" => "{ 'user-can-drag': !disabled }",
|
%li.card{ ":class" => "{ 'user-can-drag': !disabled && issue.id, 'is-disabled': disabled || !issue.id }",
|
||||||
":index" => "index" }
|
":index" => "index" }
|
||||||
%h4.card-title
|
%h4.card-title
|
||||||
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
|
= icon("eye-slash", class: "confidential-icon", "v-if" => "issue.confidential")
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
":title" => "issue.title" }
|
":title" => "issue.title" }
|
||||||
{{ issue.title }}
|
{{ issue.title }}
|
||||||
.card-footer
|
.card-footer
|
||||||
%span.card-number
|
%span.card-number{ "v-if" => "issue.id" }
|
||||||
= precede '#' do
|
= precede '#' do
|
||||||
{{ issue.id }}
|
{{ issue.id }}
|
||||||
%button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels",
|
%button.label.color-label.has-tooltip{ "v-for" => "label in issue.labels",
|
||||||
|
|
|
@ -425,7 +425,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only:
|
||||||
post :generate
|
post :generate
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :issues, only: [:index]
|
resources :issues, only: [:index, :create]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@ require 'spec_helper'
|
||||||
describe Projects::Boards::IssuesController do
|
describe Projects::Boards::IssuesController do
|
||||||
let(:project) { create(:project_with_board) }
|
let(:project) { create(:project_with_board) }
|
||||||
let(:user) { create(:user) }
|
let(:user) { create(:user) }
|
||||||
|
let(:guest) { create(:user) }
|
||||||
|
|
||||||
let(:planning) { create(:label, project: project, name: 'Planning') }
|
let(:planning) { create(:label, project: project, name: 'Planning') }
|
||||||
let(:development) { create(:label, project: project, name: 'Development') }
|
let(:development) { create(:label, project: project, name: 'Development') }
|
||||||
|
@ -12,6 +13,7 @@ describe Projects::Boards::IssuesController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.team << [user, :master]
|
project.team << [user, :master]
|
||||||
|
project.team << [guest, :guest]
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET index' do
|
describe 'GET index' do
|
||||||
|
@ -61,6 +63,60 @@ describe Projects::Boards::IssuesController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'POST create' do
|
||||||
|
context 'with valid params' do
|
||||||
|
it 'returns a successful 200 response' do
|
||||||
|
create_issue user: user, list: list1, title: 'New issue'
|
||||||
|
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the created issue' do
|
||||||
|
create_issue user: user, list: list1, title: 'New issue'
|
||||||
|
|
||||||
|
expect(response).to match_response_schema('issue')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with invalid params' do
|
||||||
|
context 'when title is nil' do
|
||||||
|
it 'returns an unprocessable entity 422 response' do
|
||||||
|
create_issue user: user, list: list1, title: nil
|
||||||
|
|
||||||
|
expect(response).to have_http_status(422)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when list does not belongs to project board' do
|
||||||
|
it 'returns a not found 404 response' do
|
||||||
|
list = create(:list)
|
||||||
|
|
||||||
|
create_issue user: user, list: list, title: 'New issue'
|
||||||
|
|
||||||
|
expect(response).to have_http_status(404)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with unauthorized user' do
|
||||||
|
it 'returns a forbidden 403 response' do
|
||||||
|
create_issue user: guest, list: list1, title: 'New issue'
|
||||||
|
|
||||||
|
expect(response).to have_http_status(403)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_issue(user:, list:, title:)
|
||||||
|
sign_in(user)
|
||||||
|
|
||||||
|
post :create, namespace_id: project.namespace.to_param,
|
||||||
|
project_id: project.to_param,
|
||||||
|
list_id: list.to_param,
|
||||||
|
issue: { title: title },
|
||||||
|
format: :json
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'PATCH update' do
|
describe 'PATCH update' do
|
||||||
let(:issue) { create(:labeled_issue, project: project, labels: [planning]) }
|
let(:issue) { create(:labeled_issue, project: project, labels: [planning]) }
|
||||||
|
|
||||||
|
@ -93,13 +149,7 @@ describe Projects::Boards::IssuesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with unauthorized user' do
|
context 'with unauthorized user' do
|
||||||
let(:guest) { create(:user) }
|
it 'returns a forbidden 403 response' do
|
||||||
|
|
||||||
before do
|
|
||||||
project.team << [guest, :guest]
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a successful 403 response' do
|
|
||||||
move user: guest, issue: issue, from_list_id: list1.id, to_list_id: list2.id
|
move user: guest, issue: issue, from_list_id: list1.id, to_list_id: list2.id
|
||||||
|
|
||||||
expect(response).to have_http_status(403)
|
expect(response).to have_http_status(403)
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe 'Issue Boards new issue', feature: true, js: true do
|
||||||
|
include WaitForAjax
|
||||||
|
include WaitForVueResource
|
||||||
|
|
||||||
|
let(:project) { create(:project_with_board, :public) }
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
context 'authorized user' do
|
||||||
|
before do
|
||||||
|
project.team << [user, :master]
|
||||||
|
|
||||||
|
login_as(user)
|
||||||
|
|
||||||
|
visit namespace_project_board_path(project.namespace, project)
|
||||||
|
wait_for_vue_resource
|
||||||
|
|
||||||
|
expect(page).to have_selector('.board', count: 3)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays new issue button' do
|
||||||
|
expect(page).to have_selector('.board-issue-count-holder .btn', count: 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not display new issue button in done list' do
|
||||||
|
page.within('.board:nth-child(3)') do
|
||||||
|
expect(page).not_to have_selector('.board-issue-count-holder .btn')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows form when clicking button' do
|
||||||
|
page.within(first('.board')) do
|
||||||
|
find('.board-issue-count-holder .btn').click
|
||||||
|
|
||||||
|
expect(page).to have_selector('.board-new-issue-form')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'hides form when clicking cancel' do
|
||||||
|
page.within(first('.board')) do
|
||||||
|
find('.board-issue-count-holder .btn').click
|
||||||
|
|
||||||
|
expect(page).to have_selector('.board-new-issue-form')
|
||||||
|
|
||||||
|
click_button 'Cancel'
|
||||||
|
|
||||||
|
expect(page).to have_selector('.board-new-issue-form', visible: false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates new issue' do
|
||||||
|
page.within(first('.board')) do
|
||||||
|
find('.board-issue-count-holder .btn').click
|
||||||
|
end
|
||||||
|
|
||||||
|
page.within(first('.board-new-issue-form')) do
|
||||||
|
find('.form-control').set('bug')
|
||||||
|
click_button 'Submit issue'
|
||||||
|
end
|
||||||
|
|
||||||
|
wait_for_vue_resource
|
||||||
|
|
||||||
|
page.within(first('.board .board-issue-count')) do
|
||||||
|
expect(page).to have_content('1')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'unauthorized user' do
|
||||||
|
before do
|
||||||
|
visit namespace_project_board_path(project.namespace, project)
|
||||||
|
wait_for_vue_resource
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not display new issue button' do
|
||||||
|
expect(page).to have_selector('.board-issue-count-holder .btn', count: 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,33 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Boards::Issues::CreateService, services: true do
|
||||||
|
describe '#execute' do
|
||||||
|
let(:project) { create(:project_with_board) }
|
||||||
|
let(:board) { project.board }
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
let(:label) { create(:label, project: project, name: 'in-progress') }
|
||||||
|
let!(:list) { create(:list, board: board, label: label, position: 0) }
|
||||||
|
|
||||||
|
subject(:service) { described_class.new(project, user, title: 'New issue') }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.team << [user, :developer]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'delegates the create proceedings to Issues::CreateService' do
|
||||||
|
expect_any_instance_of(Issues::CreateService).to receive(:execute).once
|
||||||
|
|
||||||
|
service.execute(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a new issue' do
|
||||||
|
expect { service.execute(list) }.to change(project.issues, :count).by(1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds the label of the list to the issue' do
|
||||||
|
issue = service.execute(list)
|
||||||
|
|
||||||
|
expect(issue.labels).to eq [label]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue