Commit fbfe3066 authored by Rémy Coutable's avatar Rémy Coutable

Merge remote-tracking branch 'origin/master' into ce-to-ee-2018-01-15

Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parents 40678da6 4e6a9f1d
......@@ -55,7 +55,6 @@ import ShortcutsIssuable from './shortcuts_issuable';
import U2FAuthenticate from './u2f/authenticate';
import Members from './members';
import memberExpirationDate from './member_expiration_date';
import DueDateSelectors from './due_date_select';
import Diff from './diff';
import ProjectLabelSubscription from './project_label_subscription';
import SearchAutocomplete from './search_autocomplete';
......@@ -208,18 +207,28 @@ import initLDAPGroupsSelect from 'ee/ldap_groups_select'; // eslint-disable-line
.catch(fail);
break;
case 'projects:milestones:new':
case 'projects:milestones:create':
import('./pages/projects/milestones/new')
.then(callDefault)
.catch(fail);
break;
case 'projects:milestones:edit':
case 'projects:milestones:update':
new ZenMode();
new DueDateSelectors();
new GLForm($('.milestone-form'), true);
import('./pages/projects/milestones/edit')
.then(callDefault)
.catch(fail);
break;
case 'groups:milestones:new':
case 'groups:milestones:create':
import('./pages/groups/milestones/new')
.then(callDefault)
.catch(fail);
break;
case 'groups:milestones:edit':
case 'groups:milestones:update':
new ZenMode();
new DueDateSelectors();
new GLForm($('.milestone-form'), false);
import('./pages/groups/milestones/edit')
.then(callDefault)
.catch(fail);
break;
case 'groups:epics:show':
new ZenMode();
......
import initForm from '../../../../shared/milestones/form';
export default () => initForm(false);
import initForm from '../../../../shared/milestones/form';
export default () => initForm(false);
import initForm from '../../../../shared/milestones/form';
export default () => initForm();
import initForm from '../../../../shared/milestones/form';
export default () => initForm();
import initPathLocks from 'ee/path_locks';
import TreeView from '../../../../tree';
import ShortcutsNavigation from '../../../../shortcuts_navigation';
import BlobViewer from '../../../../blob/viewer';
......@@ -11,5 +12,11 @@ export default () => {
new NewCommitForm($('.js-create-dir-form')); // eslint-disable-line no-new
$('#tree-slider').waitForImages(() =>
ajaxGet(document.querySelector('.js-tree-content').dataset.logsPath));
};
if (document.querySelector('.js-tree-content').dataset.pathLocksAvailable === 'true') {
initPathLocks(
document.querySelector('.js-tree-content').dataset.pathLocksToggle,
document.querySelector('.js-tree-content').dataset.pathLocksPath,
);
}
};
import ZenMode from '../../zen_mode';
import DueDateSelectors from '../../due_date_select';
import GLForm from '../../gl_form';
export default (initGFM = true) => {
new ZenMode(); // eslint-disable-line no-new
new DueDateSelectors(); // eslint-disable-line no-new
new GLForm($('.milestone-form'), initGFM); // eslint-disable-line no-new
};
......@@ -9,6 +9,7 @@ module Ci
MissingDependenciesError = Class.new(StandardError)
belongs_to :project, inverse_of: :builds
belongs_to :runner
belongs_to :trigger_request
belongs_to :erased_by, class_name: 'User'
......
......@@ -9,7 +9,7 @@ module Ci
prepend ::EE::Ci::Pipeline
belongs_to :project
belongs_to :project, inverse_of: :pipelines
belongs_to :user
belongs_to :auto_canceled_by, class_name: 'Ci::Pipeline'
belongs_to :pipeline_schedule, class_name: 'Ci::PipelineSchedule'
......
......@@ -204,13 +204,13 @@ class Project < ActiveRecord::Base
has_many :container_repositories, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :commit_statuses
has_many :pipelines, class_name: 'Ci::Pipeline'
has_many :pipelines, class_name: 'Ci::Pipeline', inverse_of: :project
# Ci::Build objects store data on the file system such as artifact files and
# build traces. Currently there's no efficient way of removing this data in
# bulk that doesn't involve loading the rows into memory. As a result we're
# still using `dependent: :destroy` here.
has_many :builds, class_name: 'Ci::Build', dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :builds, class_name: 'Ci::Build', inverse_of: :project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_many :build_trace_section_names, class_name: 'Ci::BuildTraceSectionName'
has_many :runner_projects, class_name: 'Ci::RunnerProject'
has_many :runners, through: :runner_projects, source: :runner, class_name: 'Ci::Runner'
......
......@@ -17,6 +17,7 @@ class SystemNoteMetadata < ActiveRecord::Base
outdated
approved unapproved relate unrelate
epic_issue_added issue_added_to_epic epic_issue_removed issue_removed_from_epic
epic_issue_moved issue_changed_epic
].freeze
validates :note, presence: true
......
......@@ -576,6 +576,20 @@ module SystemNoteService
create_note(NoteSummary.new(epic, nil, user, body, action: action))
end
def epic_issue_moved(from_epic, issue, to_epic, user)
epic_issue_moved_act(from_epic, issue, to_epic, user, verb: 'added', direction: 'from')
epic_issue_moved_act(to_epic, issue, from_epic, user, verb: 'moved', direction: 'to')
end
def epic_issue_moved_act(subject_epic, issue, object_epic, user, verb:, direction:)
action = 'epic_issue_moved'
body = "#{verb} issue #{issue.to_reference(subject_epic.group)} #{direction}" \
" epic #{subject_epic.to_reference(object_epic.group)}"
create_note(NoteSummary.new(object_epic, nil, user, body, action: action))
end
def issue_on_epic(issue, epic, user, type)
return unless validate_epic_issue_action_type(type)
......@@ -592,6 +606,13 @@ module SystemNoteService
create_note(NoteSummary.new(issue, issue.project, user, body, action: action))
end
def issue_epic_change(issue, epic, user)
body = "changed epic to #{epic.to_reference(issue.project)}"
action = 'issue_changed_epic'
create_note(NoteSummary.new(issue, issue.project, user, body, action: action))
end
def validate_epic_issue_action_type(type)
[:added, :removed].include?(type)
end
......
---
title: Add system notes when moving issues between epics
merge_request:
author:
type: added
......@@ -4,16 +4,30 @@ module EpicIssues
def relate_issues(referenced_issue)
link = EpicIssue.find_or_initialize_by(issue: referenced_issue)
params = if link.persisted?
{ issue_moved: true, original_epic: link.epic }
else
{}
end
link.epic = issuable
link.move_to_start
link.save!
link
yield params
end
def create_notes(referenced_issue)
SystemNoteService.epic_issue(issuable, referenced_issue, current_user, :added)
SystemNoteService.issue_on_epic(referenced_issue, issuable, current_user, :added)
def create_notes(referenced_issue, params)
if params[:issue_moved]
SystemNoteService.epic_issue_moved(
params[:original_epic], referenced_issue, issuable, current_user
)
SystemNoteService.issue_epic_change(referenced_issue, issuable, current_user)
else
SystemNoteService.epic_issue(issuable, referenced_issue, current_user, :added)
SystemNoteService.issue_on_epic(referenced_issue, issuable, current_user, :added)
end
end
def extractor_context
......
......@@ -19,11 +19,9 @@ module IssuableLinks
def create_issue_links
referenced_issues.each do |referenced_issue|
link = relate_issues(referenced_issue)
next unless link.persisted?
create_notes(referenced_issue)
relate_issues(referenced_issue) do |params|
create_notes(referenced_issue, params)
end
end
end
......
module IssueLinks
class CreateService < IssuableLinks::CreateService
def relate_issues(referenced_issue)
IssueLink.create(source: issuable, target: referenced_issue)
link = IssueLink.create(source: issuable, target: referenced_issue)
yield if link.persisted?
end
def linkable_issues(issues)
issues.select { |issue| can?(current_user, :admin_issue_link, issue) }
end
def create_notes(referenced_issue)
def create_notes(referenced_issue, params)
SystemNoteService.relate_issue(issuable, referenced_issue, current_user)
SystemNoteService.relate_issue(referenced_issue, issuable, current_user)
end
......
......@@ -223,6 +223,37 @@ describe EpicIssues::CreateService do
it 'returns success status' do
is_expected.to eq(status: :success)
end
it 'creates 3 system notes' do
expect { subject }.to change { Note.count }.from(0).to(3)
end
it 'creates a note correctly for the original epic' do
subject
note = Note.find_by(system: true, noteable_type: 'Epic', noteable_id: epic.id)
expect(note.note).to eq("moved issue #{issue.to_reference(epic.group)} to epic #{another_epic.to_reference(epic.group)}")
expect(note.system_note_metadata.action).to eq('epic_issue_moved')
end
it 'creates a note correctly for the new epic' do
subject
note = Note.find_by(system: true, noteable_type: 'Epic', noteable_id: another_epic.id)
expect(note.note).to eq("added issue #{issue.to_reference(epic.group)} from epic #{epic.to_reference(epic.group)}")
expect(note.system_note_metadata.action).to eq('epic_issue_moved')
end
it 'creates a note correctly for the issue' do
subject
note = Note.find_by(system: true, noteable_type: 'Issue', noteable_id: issue.id)
expect(note.note).to eq("changed epic to #{another_epic.to_reference(issue.project)}")
expect(note.system_note_metadata.action).to eq('issue_changed_epic')
end
end
context 'when issue from non group project is given' do
......
......@@ -26,6 +26,13 @@ describe Ci::Build do
it { is_expected.to be_a(ArtifactMigratable) }
describe 'associations' do
it 'has a bidirectional relationship with projects' do
expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:builds)
expect(Project.reflect_on_association(:builds).has_inverse?).to eq(:project)
end
end
describe 'callbacks' do
context 'when running after_create callback' do
it 'triggers asynchronous build hooks worker' do
......
......@@ -32,6 +32,13 @@ describe Ci::Pipeline, :mailer do
it { is_expected.to respond_to :short_sha }
it { is_expected.to delegate_method(:full_path).to(:project).with_prefix }
describe 'associations' do
it 'has a bidirectional relationship with projects' do
expect(described_class.reflect_on_association(:project).has_inverse?).to eq(:pipelines)
expect(Project.reflect_on_association(:pipelines).has_inverse?).to eq(:project)
end
end
describe '#source' do
context 'when creating new pipeline' do
let(:pipeline) do
......
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