Commit bdb69dfe authored by Dmytro Zaporozhets's avatar Dmytro Zaporozhets

Merge branch '13847-add-epic-dropdown-new-issue-page' into 'master'

Add Epics select dropdown in New Issue page

Closes #13847

See merge request gitlab-org/gitlab!32572
parents 3986aa16 88c4d24f
...@@ -403,6 +403,10 @@ module Issuable ...@@ -403,6 +403,10 @@ module Issuable
participants(user).include?(user) participants(user).include?(user)
end end
def can_assign_epic?(user)
false
end
def to_hook_data(user, old_associations: {}) def to_hook_data(user, old_associations: {})
changes = previous_changes changes = previous_changes
......
...@@ -31,7 +31,7 @@ ...@@ -31,7 +31,7 @@
= form.label :confidential, class: 'form-check-label' do = form.label :confidential, class: 'form-check-label' do
This issue is confidential and should only be visible to team members with at least Reporter access. This issue is confidential and should only be visible to team members with at least Reporter access.
= render 'shared/issuable/form/metadata', issuable: issuable, form: form = render 'shared/issuable/form/metadata', issuable: issuable, form: form, project: project
= render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form = render_if_exists 'shared/issuable/approvals', issuable: issuable, presenter: presenter, form: form
......
- project = local_assigns.fetch(:project)
- issuable = local_assigns.fetch(:issuable) - issuable = local_assigns.fetch(:issuable)
- return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project) - return unless can?(current_user, :"admin_#{issuable.to_ability_name}", issuable.project)
...@@ -10,6 +11,9 @@ ...@@ -10,6 +11,9 @@
%div{ class: (has_due_date ? "col-lg-6" : "col-12") } %div{ class: (has_due_date ? "col-lg-6" : "col-12") }
.form-group.row.merge-request-assignee .form-group.row.merge-request-assignee
= render "shared/issuable/form/metadata_issuable_assignee", issuable: issuable, form: form, has_due_date: has_due_date = render "shared/issuable/form/metadata_issuable_assignee", issuable: issuable, form: form, has_due_date: has_due_date
= render_if_exists "shared/issuable/form/epic", issuable: issuable, form: form, project: project
.form-group.row.issue-milestone .form-group.row.issue-milestone
= form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}" = form.label :milestone_id, "Milestone", class: "col-form-label #{has_due_date ? "col-md-2 col-lg-4" : "col-sm-2"}"
.col-sm-10{ class: ("col-md-8" if has_due_date) } .col-sm-10{ class: ("col-md-8" if has_due_date) }
...@@ -22,11 +26,11 @@ ...@@ -22,11 +26,11 @@
.issuable-form-select-holder .issuable-form-select-holder
= render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label" = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }, dropdown_title: "Select label"
= render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form
= render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form = render_if_exists "shared/issuable/form/merge_request_blocks", issuable: issuable, form: form
- if has_due_date - if has_due_date || issuable.supports_weight?
.col-lg-6 .col-lg-6
= render_if_exists "shared/issuable/form/weight", issuable: issuable, form: form
.form-group.row .form-group.row
= form.label :due_date, "Due date", class: "col-form-label col-md-2 col-lg-4" = form.label :due_date, "Due date", class: "col-form-label col-md-2 col-lg-4"
.col-8 .col-8
......
...@@ -4,24 +4,25 @@ group: Project Management ...@@ -4,24 +4,25 @@ group: Project Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
--- ---
# Managing Issues # Managing issues
[GitLab Issues](index.md) are the fundamental medium for collaborating on ideas and [GitLab Issues](index.md) are the fundamental medium for collaborating on ideas and
planning work in GitLab. [Creating](#create-a-new-issue), [moving](#moving-issues), planning work in GitLab. [Creating](#create-a-new-issue), [moving](#moving-issues),
[closing](#closing-issues), and [deleting](#deleting-issues) are key actions that [closing](#closing-issues), and [deleting](#deleting-issues) are key actions that
you can do with issues. you can do with issues.
## Create a new Issue ## Create a new issue
When you create a new issue, you'll be prompted to fill in the [data and fields of the issue](issue_data_and_actions.md), as illustrated below. If you know When you create a new issue, you'll be prompted to fill in the [data and fields of the issue](issue_data_and_actions.md),
the values you want to assign to an issue, you can use the [Quick actions](../quick_actions.md) as illustrated below. If you know the values you want to assign to an issue, you can use the
feature to input values, instead of selecting them from lists. [Quick actions](../quick_actions.md) feature to input values, instead of selecting them from lists.
![New issue from the issues list](img/new_issue.png) While creating an issue, you can associate it to an existing epic from current group by
selecting it using **Epic** dropdown.
### Accessing the new Issue form ### Accessing the New Issue form
There are many ways to get to the new Issue form from within a project: There are many ways to get to the New Issue form from within a project:
- Navigate to your **Project's Dashboard** > **Issues** > **New Issue**: - Navigate to your **Project's Dashboard** > **Issues** > **New Issue**:
...@@ -42,9 +43,28 @@ There are many ways to get to the new Issue form from within a project: ...@@ -42,9 +43,28 @@ There are many ways to get to the new Issue form from within a project:
![From the issue board](img/new_issue_from_issue_board.png) ![From the issue board](img/new_issue_from_issue_board.png)
### Elements of the New Issue form
> Ability to add the new issue to an epic [was introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13847)
> in [GitLab Premium](https://about.gitlab.com/pricing/) 13.1.
![New issue from the issues list](img/new_issue_v13_1.png)
When you're creating a new issue, these are the fields you can fill in:
- Title
- Description
- Checkbox to make the issue confidential
- Assignee
- Weight
- Epic **(PREMIUM)**
- Due date
- Milestone
- Labels
### New issue from the group-level Issue Tracker ### New issue from the group-level Issue Tracker
Go to the Group dashboard and click "Issues" in the sidebar to visit the Issue Tracker Go to the Group dashboard and click **Issues** in the sidebar to visit the Issue Tracker
for all projects in your Group. Select the project you'd like to add an issue for for all projects in your Group. Select the project you'd like to add an issue for
using the dropdown button at the top-right of the page. using the dropdown button at the top-right of the page.
...@@ -153,7 +173,7 @@ issues.each do |issue| ...@@ -153,7 +173,7 @@ issues.each do |issue|
end; nil end; nil
``` ```
## Closing Issues ## Closing issues
When you decide that an issue is resolved, or no longer needed, you can close the issue When you decide that an issue is resolved, or no longer needed, you can close the issue
using the close button: using the close button:
...@@ -251,7 +271,7 @@ In order to change the default issue closing pattern, GitLab administrators must ...@@ -251,7 +271,7 @@ In order to change the default issue closing pattern, GitLab administrators must
[`gitlab.rb` or `gitlab.yml` file](../../../administration/issue_closing_pattern.md) [`gitlab.rb` or `gitlab.yml` file](../../../administration/issue_closing_pattern.md)
of your installation. of your installation.
## Deleting Issues ## Deleting issues
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/2982) in GitLab 8.6 > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/2982) in GitLab 8.6
......
import EpicsSelect from 'ee/vue_shared/components/sidebar/epics_select/epics_select_bundle';
import WeightSelect from 'ee/weight_select'; import WeightSelect from 'ee/weight_select';
import initForm from '~/pages/projects/issues/form'; import initForm from '~/pages/projects/issues/form';
export default () => { export default () => {
// eslint-disable-next-line no-new
new EpicsSelect();
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new WeightSelect(); new WeightSelect();
initForm(); initForm();
......
import Vue from 'vue';
import EpicsSelect from 'ee/vue_shared/components/sidebar/epics_select/base.vue';
import { noneEpic } from 'ee/vue_shared/constants';
import { DropdownVariant } from 'ee/vue_shared/components/sidebar/epics_select/constants';
export default () => {
const el = document.getElementById('js-epic-select-root');
const epicFormFieldEl = document.getElementById('issue_epic_id');
if (!el && !epicFormFieldEl) {
return false;
}
return new Vue({
el,
components: {
EpicsSelect,
},
data() {
return {
selectedEpic: noneEpic,
};
},
methods: {
handleEpicSelect(selectedEpic) {
this.selectedEpic = selectedEpic;
epicFormFieldEl.setAttribute('value', selectedEpic.id);
},
},
render(createElement) {
return createElement('epics-select', {
props: {
groupId: parseInt(el.dataset.groupId, 10),
issueId: 0,
epicIssueId: 0,
canEdit: true,
initialEpic: this.selectedEpic,
initialEpicLoading: false,
variant: DropdownVariant.Standalone,
},
on: {
onEpicSelect: this.handleEpicSelect.bind(this),
},
});
},
});
};
...@@ -121,6 +121,10 @@ module EE ...@@ -121,6 +121,10 @@ module EE
project&.feature_available?(:issue_weights) project&.feature_available?(:issue_weights)
end end
def can_assign_epic?(user)
user&.can?(:admin_epic, project.group)
end
def related_issues(current_user, preload: nil) def related_issues(current_user, preload: nil)
related_issues = ::Issue related_issues = ::Issue
.select(['issues.*', 'issue_links.id AS issue_link_id', .select(['issues.*', 'issue_links.id AS issue_link_id',
......
- project = local_assigns.fetch(:project)
- issuable = local_assigns.fetch(:issuable)
- return unless issuable.can_assign_epic?(current_user)
- form = local_assigns.fetch(:form)
.form-group.row.issue-epic
= form.label :label_ids, class: "col-form-label col-md-2 col-lg-4" do
= _('Epic')
.col-md-10.col-lg-8
.issuable-form-select-holder
%input{ id: 'issue_epic_id', type: 'hidden', name: 'issue[epic_id]' }
#js-epic-select-root{ data: { group_id: project.group.id } }
- issuable = local_assigns.fetch(:issuable) - issuable = local_assigns.fetch(:issuable)
- return unless issuable.supports_weight?
- has_due_date = issuable.has_attribute?(:due_date) - has_due_date = issuable.has_attribute?(:due_date)
- form = local_assigns.fetch(:form) - form = local_assigns.fetch(:form)
......
---
title: Add ability to select Epic while creating a New Issue
merge_request: 32572
author:
type: added
...@@ -3,13 +3,16 @@ ...@@ -3,13 +3,16 @@
require "spec_helper" require "spec_helper"
RSpec.describe "User creates issue", :js do RSpec.describe "User creates issue", :js do
let(:project) { create(:project_empty_repo, :public) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group, :public) }
let(:project) { create(:project_empty_repo, :public, namespace: group) }
let!(:epic) { create(:epic, group: group, title: 'Sample epic', author: user) }
before do before do
stub_licensed_features(issue_weights: true) stub_licensed_features(issue_weights: true)
stub_licensed_features(epics: true)
project.add_developer(user) group.add_developer(user)
sign_in(user) sign_in(user)
visit(new_project_issue_path(project)) visit(new_project_issue_path(project))
...@@ -32,4 +35,28 @@ RSpec.describe "User creates issue", :js do ...@@ -32,4 +35,28 @@ RSpec.describe "User creates issue", :js do
expect(page).to have_content(issue_title) expect(page).to have_content(issue_title)
end end
end end
context "with epic set" do
it "creates issue" do
issue_title = "500 error on profile"
fill_in("Title", with: issue_title)
page.within('.issue-epic .js-epic-block') do
page.find('.js-epic-select').click
wait_for_requests
page.find('.dropdown-content .gl-link', text: epic.title).click
end
click_button("Submit issue")
wait_for_all_requests
page.within(".js-epic-block .js-epic-label") do
expect(page).to have_content(epic.title)
end
expect(page).to have_content(issue_title)
end
end
end end
...@@ -678,4 +678,49 @@ RSpec.describe Issue do ...@@ -678,4 +678,49 @@ RSpec.describe Issue do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
end end
describe '#can_assign_epic?' do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:project) { create(:project, group: group) }
let(:issue) { create(:issue, project: project) }
subject { issue.can_assign_epic?(user) }
context 'when epics feature is available' do
before do
stub_licensed_features(epics: true)
end
context 'when a user is not a project member' do
it 'returns false' do
expect(subject).to be_falsey
end
end
context 'when a user is a project member' do
it 'returns false' do
project.add_developer(user)
expect(subject).to be_falsey
end
end
context 'when a user is a group member' do
it 'returns true' do
group.add_developer(user)
expect(subject).to be_truthy
end
end
end
context 'when epics feature is not available' do
it 'returns false' do
group.add_developer(user)
expect(subject).to be_falsey
end
end
end
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