Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
gitlab-ce
Commits
08bbb9fc
Commit
08bbb9fc
authored
Mar 09, 2017
by
Douwe Maan
Committed by
Luke "Jared" Bennett
Apr 05, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add option to start a new discussion on an MR
parent
8bdfee8b
Changes
76
Hide whitespace changes
Inline
Side-by-side
Showing
76 changed files
with
1323 additions
and
982 deletions
+1323
-982
app/assets/javascripts/files_comment_button.js
app/assets/javascripts/files_comment_button.js
+18
-8
app/assets/javascripts/notes.js
app/assets/javascripts/notes.js
+47
-20
app/controllers/projects/commit_controller.rb
app/controllers/projects/commit_controller.rb
+9
-11
app/controllers/projects/discussions_controller.rb
app/controllers/projects/discussions_controller.rb
+1
-1
app/controllers/projects/issues_controller.rb
app/controllers/projects/issues_controller.rb
+3
-7
app/controllers/projects/merge_requests_controller.rb
app/controllers/projects/merge_requests_controller.rb
+5
-25
app/controllers/projects/notes_controller.rb
app/controllers/projects/notes_controller.rb
+48
-38
app/controllers/projects/snippets_controller.rb
app/controllers/projects/snippets_controller.rb
+4
-1
app/finders/notes_finder.rb
app/finders/notes_finder.rb
+32
-22
app/helpers/notes_helper.rb
app/helpers/notes_helper.rb
+23
-26
app/models/commit.rb
app/models/commit.rb
+5
-0
app/models/commit_discussion.rb
app/models/commit_discussion.rb
+9
-0
app/models/concerns/note_on_diff.rb
app/models/concerns/note_on_diff.rb
+0
-8
app/models/concerns/noteable.rb
app/models/concerns/noteable.rb
+17
-0
app/models/concerns/resolvable_note.rb
app/models/concerns/resolvable_note.rb
+61
-0
app/models/diff_discussion.rb
app/models/diff_discussion.rb
+69
-0
app/models/diff_note.rb
app/models/diff_note.rb
+22
-93
app/models/discussion.rb
app/models/discussion.rb
+71
-81
app/models/discussion_note.rb
app/models/discussion_note.rb
+9
-0
app/models/individual_note_discussion.rb
app/models/individual_note_discussion.rb
+13
-0
app/models/issue.rb
app/models/issue.rb
+1
-0
app/models/legacy_diff_discussion.rb
app/models/legacy_diff_discussion.rb
+21
-0
app/models/legacy_diff_note.rb
app/models/legacy_diff_note.rb
+2
-8
app/models/merge_request.rb
app/models/merge_request.rb
+13
-24
app/models/note.rb
app/models/note.rb
+74
-38
app/models/sent_notification.rb
app/models/sent_notification.rb
+32
-31
app/models/simple_discussion.rb
app/models/simple_discussion.rb
+9
-0
app/models/snippet.rb
app/models/snippet.rb
+1
-0
app/services/concerns/issues/resolve_discussions.rb
app/services/concerns/issues/resolve_discussions.rb
+1
-1
app/services/notes/build_service.rb
app/services/notes/build_service.rb
+29
-0
app/services/notes/create_service.rb
app/services/notes/create_service.rb
+2
-4
app/services/system_note_service.rb
app/services/system_note_service.rb
+3
-5
app/views/discussions/_discussion.html.haml
app/views/discussions/_discussion.html.haml
+8
-6
app/views/discussions/_notes.html.haml
app/views/discussions/_notes.html.haml
+7
-6
app/views/discussions/_resolve_all.html.haml
app/views/discussions/_resolve_all.html.haml
+8
-9
app/views/projects/commit/show.html.haml
app/views/projects/commit/show.html.haml
+1
-0
app/views/projects/notes/_form.html.haml
app/views/projects/notes/_form.html.haml
+14
-3
app/views/projects/notes/_note.html.haml
app/views/projects/notes/_note.html.haml
+1
-1
app/views/projects/notes/_notes.html.haml
app/views/projects/notes/_notes.html.haml
+2
-2
changelogs/unreleased/new-resolvable-discussion.yml
changelogs/unreleased/new-resolvable-discussion.yml
+4
-0
db/migrate/20161128095517_add_in_reply_to_discussion_id_to_sent_notifications.rb
...17_add_in_reply_to_discussion_id_to_sent_notifications.rb
+29
-0
db/migrate/20170308220217_add_index_to_note_original_discussion_id.rb
...0170308220217_add_index_to_note_original_discussion_id.rb
+18
-0
db/schema.rb
db/schema.rb
+2
-0
lib/gitlab/email/handler/create_note_handler.rb
lib/gitlab/email/handler/create_note_handler.rb
+1
-7
spec/controllers/projects/commit_controller_spec.rb
spec/controllers/projects/commit_controller_spec.rb
+1
-1
spec/controllers/projects/discussions_controller_spec.rb
spec/controllers/projects/discussions_controller_spec.rb
+1
-1
spec/controllers/projects/issues_controller_spec.rb
spec/controllers/projects/issues_controller_spec.rb
+1
-1
spec/controllers/projects/merge_requests_controller_spec.rb
spec/controllers/projects/merge_requests_controller_spec.rb
+1
-1
spec/controllers/projects/notes_controller_spec.rb
spec/controllers/projects/notes_controller_spec.rb
+10
-1
spec/factories/discussions.rb
spec/factories/discussions.rb
+5
-0
spec/factories/notes.rb
spec/factories/notes.rb
+9
-0
spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
...ues/create_issue_for_discussions_in_merge_request_spec.rb
+1
-1
spec/features/notes_on_merge_requests_spec.rb
spec/features/notes_on_merge_requests_spec.rb
+1
-1
spec/finders/notes_finder_spec.rb
spec/finders/notes_finder_spec.rb
+4
-0
spec/models/commit_discussion_spec.rb
spec/models/commit_discussion_spec.rb
+5
-0
spec/models/concerns/noteable_spec.rb
spec/models/concerns/noteable_spec.rb
+5
-0
spec/models/concerns/resolvable_note_spec.rb
spec/models/concerns/resolvable_note_spec.rb
+276
-0
spec/models/diff_discussion_spec.rb
spec/models/diff_discussion_spec.rb
+24
-0
spec/models/diff_note_spec.rb
spec/models/diff_note_spec.rb
+4
-283
spec/models/discussion_note_spec.rb
spec/models/discussion_note_spec.rb
+5
-0
spec/models/discussion_spec.rb
spec/models/discussion_spec.rb
+57
-40
spec/models/individual_note_discussion_spec.rb
spec/models/individual_note_discussion_spec.rb
+5
-0
spec/models/legacy_diff_discussion_spec.rb
spec/models/legacy_diff_discussion_spec.rb
+5
-0
spec/models/legacy_diff_note_spec.rb
spec/models/legacy_diff_note_spec.rb
+0
-101
spec/models/merge_request_spec.rb
spec/models/merge_request_spec.rb
+36
-32
spec/models/note_spec.rb
spec/models/note_spec.rb
+92
-25
spec/models/sent_notification_spec.rb
spec/models/sent_notification_spec.rb
+5
-0
spec/models/simple_discussion_spec.rb
spec/models/simple_discussion_spec.rb
+5
-0
spec/requests/api/v3/issues_spec.rb
spec/requests/api/v3/issues_spec.rb
+1
-1
spec/services/discussions/resolve_service_spec.rb
spec/services/discussions/resolve_service_spec.rb
+2
-2
spec/services/issues/build_service_spec.rb
spec/services/issues/build_service_spec.rb
+2
-2
spec/services/issues/create_service_spec.rb
spec/services/issues/create_service_spec.rb
+1
-1
spec/services/notes/build_service_spec.rb
spec/services/notes/build_service_spec.rb
+5
-0
spec/services/notes/create_service_spec.rb
spec/services/notes/create_service_spec.rb
+3
-0
spec/services/notification_service_spec.rb
spec/services/notification_service_spec.rb
+1
-1
spec/services/system_note_service_spec.rb
spec/services/system_note_service_spec.rb
+1
-1
No files found.
app/assets/javascripts/files_comment_button.js
View file @
08bbb9fc
...
@@ -5,7 +5,7 @@
...
@@ -5,7 +5,7 @@
let
$commentButtonTemplate
;
let
$commentButtonTemplate
;
var
bind
=
function
(
fn
,
me
)
{
return
function
()
{
return
fn
.
apply
(
me
,
arguments
);
};
};
var
bind
=
function
(
fn
,
me
)
{
return
function
()
{
return
fn
.
apply
(
me
,
arguments
);
};
};
window
.
FilesCommentButton
=
(
function
()
{
this
.
FilesCommentButton
=
(
function
()
{
var
COMMENT_BUTTON_CLASS
,
EMPTY_CELL_CLASS
,
LINE_COLUMN_CLASSES
,
LINE_CONTENT_CLASS
,
LINE_HOLDER_CLASS
,
LINE_NUMBER_CLASS
,
OLD_LINE_CLASS
,
TEXT_FILE_SELECTOR
,
UNFOLDABLE_LINE_CLASS
;
var
COMMENT_BUTTON_CLASS
,
EMPTY_CELL_CLASS
,
LINE_COLUMN_CLASSES
,
LINE_CONTENT_CLASS
,
LINE_HOLDER_CLASS
,
LINE_NUMBER_CLASS
,
OLD_LINE_CLASS
,
TEXT_FILE_SELECTOR
,
UNFOLDABLE_LINE_CLASS
;
COMMENT_BUTTON_CLASS
=
'
.add-diff-note
'
;
COMMENT_BUTTON_CLASS
=
'
.add-diff-note
'
;
...
@@ -55,14 +55,19 @@ window.FilesCommentButton = (function() {
...
@@ -55,14 +55,19 @@ window.FilesCommentButton = (function() {
textFileElement
=
this
.
getTextFileElement
(
$currentTarget
);
textFileElement
=
this
.
getTextFileElement
(
$currentTarget
);
buttonParentElement
.
append
(
this
.
buildButton
({
buttonParentElement
.
append
(
this
.
buildButton
({
discussionID
:
lineContentElement
.
attr
(
'
data-discussion-id
'
),
lineType
:
lineContentElement
.
attr
(
'
data-line-type
'
),
noteableType
:
textFileElement
.
attr
(
'
data-noteable-type
'
),
noteableType
:
textFileElement
.
attr
(
'
data-noteable-type
'
),
noteableID
:
textFileElement
.
attr
(
'
data-noteable-id
'
),
noteableID
:
textFileElement
.
attr
(
'
data-noteable-id
'
),
commitID
:
textFileElement
.
attr
(
'
data-commit-id
'
),
commitID
:
textFileElement
.
attr
(
'
data-commit-id
'
),
noteType
:
lineContentElement
.
attr
(
'
data-note-type
'
),
noteType
:
lineContentElement
.
attr
(
'
data-note-type
'
),
position
:
lineContentElement
.
attr
(
'
data-position
'
),
lineType
:
lineContentElement
.
attr
(
'
data-line-type
'
),
// LegacyDiffNote
discussionID
:
lineContentElement
.
attr
(
'
data-discussion-id
'
),
lineCode
:
lineContentElement
.
attr
(
'
data-line-code
'
),
lineCode
:
lineContentElement
.
attr
(
'
data-line-code
'
)
// DiffNote
position
:
lineContentElement
.
attr
(
'
data-position
'
)
}));
}));
};
};
...
@@ -76,14 +81,19 @@ window.FilesCommentButton = (function() {
...
@@ -76,14 +81,19 @@ window.FilesCommentButton = (function() {
FilesCommentButton
.
prototype
.
buildButton
=
function
(
buttonAttributes
)
{
FilesCommentButton
.
prototype
.
buildButton
=
function
(
buttonAttributes
)
{
return
$commentButtonTemplate
.
clone
().
attr
({
return
$commentButtonTemplate
.
clone
().
attr
({
'
data-discussion-id
'
:
buttonAttributes
.
discussionID
,
'
data-line-type
'
:
buttonAttributes
.
lineType
,
'
data-noteable-type
'
:
buttonAttributes
.
noteableType
,
'
data-noteable-type
'
:
buttonAttributes
.
noteableType
,
'
data-noteable-id
'
:
buttonAttributes
.
noteableID
,
'
data-noteable-id
'
:
buttonAttributes
.
noteableID
,
'
data-commit-id
'
:
buttonAttributes
.
commitID
,
'
data-commit-id
'
:
buttonAttributes
.
commitID
,
'
data-note-type
'
:
buttonAttributes
.
noteType
,
'
data-note-type
'
:
buttonAttributes
.
noteType
,
// LegacyDiffNote
'
data-line-code
'
:
buttonAttributes
.
lineCode
,
'
data-line-code
'
:
buttonAttributes
.
lineCode
,
'
data-position
'
:
buttonAttributes
.
position
,
'
data-discussion-id
'
:
buttonAttributes
.
discussionID
,
// DiffNote
'
data-
line-type
'
:
buttonAttributes
.
lineType
'
data-
position
'
:
buttonAttributes
.
position
});
});
};
};
...
...
app/assets/javascripts/notes.js
View file @
08bbb9fc
...
@@ -213,11 +213,7 @@ require('./task_list');
...
@@ -213,11 +213,7 @@ require('./task_list');
_this
.
last_fetched_at
=
data
.
last_fetched_at
;
_this
.
last_fetched_at
=
data
.
last_fetched_at
;
_this
.
setPollingInterval
(
data
.
notes
.
length
);
_this
.
setPollingInterval
(
data
.
notes
.
length
);
return
$
.
each
(
notes
,
function
(
i
,
note
)
{
return
$
.
each
(
notes
,
function
(
i
,
note
)
{
if
(
note
.
discussion_html
!=
null
)
{
_this
.
renderNote
(
note
);
return
_this
.
renderDiscussionNote
(
note
);
}
else
{
return
_this
.
renderNote
(
note
);
}
});
});
};
};
})(
this
)
})(
this
)
...
@@ -278,6 +274,10 @@ require('./task_list');
...
@@ -278,6 +274,10 @@ require('./task_list');
Notes
.
prototype
.
renderNote
=
function
(
note
)
{
Notes
.
prototype
.
renderNote
=
function
(
note
)
{
var
$notesList
;
var
$notesList
;
if
(
note
.
discussion_html
!=
null
)
{
return
this
.
renderDiscussionNote
(
note
);
}
if
(
!
note
.
valid
)
{
if
(
!
note
.
valid
)
{
if
(
note
.
errors
.
commands_only
)
{
if
(
note
.
errors
.
commands_only
)
{
new
Flash
(
note
.
errors
.
commands_only
,
'
notice
'
,
this
.
parentTimeline
);
new
Flash
(
note
.
errors
.
commands_only
,
'
notice
'
,
this
.
parentTimeline
);
...
@@ -323,9 +323,9 @@ require('./task_list');
...
@@ -323,9 +323,9 @@ require('./task_list');
return
;
return
;
}
}
this
.
note_ids
.
push
(
note
.
id
);
this
.
note_ids
.
push
(
note
.
id
);
form
=
$
(
"
#new-discussion-note-form-
"
+
note
.
discussion_id
);
form
=
$
(
"
.js-discussion-note-form[data-discussion-id='
"
+
note
.
discussion_id
+
"
']
"
);
if
(
(
note
.
original_discussion_id
!=
null
)
&&
form
.
length
===
0
)
{
if
(
form
.
length
===
0
)
{
form
=
$
(
"
#new-discussion-note-form-
"
+
note
.
original_discussion_id
);
form
=
$
(
"
.js-discussion-note-form[data-original-discussion-id='
"
+
note
.
original_discussion_id
+
"
']
"
);
}
}
row
=
form
.
closest
(
"
tr
"
);
row
=
form
.
closest
(
"
tr
"
);
lineType
=
this
.
isParallelView
()
?
form
.
find
(
'
#line_type
'
).
val
()
:
'
old
'
;
lineType
=
this
.
isParallelView
()
?
form
.
find
(
'
#line_type
'
).
val
()
:
'
old
'
;
...
@@ -334,8 +334,8 @@ require('./task_list');
...
@@ -334,8 +334,8 @@ require('./task_list');
note_html
.
renderGFM
();
note_html
.
renderGFM
();
// is this the first note of discussion?
// is this the first note of discussion?
discussionContainer
=
$
(
"
.notes[data-discussion-id='
"
+
note
.
discussion_id
+
"
']
"
);
discussionContainer
=
$
(
"
.notes[data-discussion-id='
"
+
note
.
discussion_id
+
"
']
"
);
if
(
(
note
.
original_discussion_id
!=
null
)
&&
discussionContainer
.
length
===
0
)
{
if
(
discussionContainer
.
length
===
0
)
{
discussionContainer
=
$
(
"
.notes[data-discussion-id='
"
+
note
.
original_discussion_id
+
"
']
"
);
discussionContainer
=
$
(
"
.notes[data-
original-
discussion-id='
"
+
note
.
original_discussion_id
+
"
']
"
);
}
}
if
(
discussionContainer
.
length
===
0
)
{
if
(
discussionContainer
.
length
===
0
)
{
if
(
!
this
.
isParallelView
()
||
row
.
hasClass
(
'
js-temp-notes-holder
'
))
{
if
(
!
this
.
isParallelView
()
||
row
.
hasClass
(
'
js-temp-notes-holder
'
))
{
...
@@ -363,7 +363,7 @@ require('./task_list');
...
@@ -363,7 +363,7 @@ require('./task_list');
// Add note to 'Changes' page discussions
// Add note to 'Changes' page discussions
discussionContainer
.
append
(
note_html
);
discussionContainer
.
append
(
note_html
);
// Init discussion on 'Discussion' page if it is merge request page
// Init discussion on 'Discussion' page if it is merge request page
if
(
$
(
'
body
'
).
attr
(
'
data-page
'
).
indexOf
(
'
projects:merge_request
'
)
===
0
)
{
if
(
$
(
'
body
'
).
attr
(
'
data-page
'
).
indexOf
(
'
projects:merge_request
'
)
===
0
||
!
note
.
diff_discussion_html
)
{
$
(
'
ul.main-notes-list
'
).
append
(
note
.
discussion_html
).
renderGFM
();
$
(
'
ul.main-notes-list
'
).
append
(
note
.
discussion_html
).
renderGFM
();
}
}
}
else
{
}
else
{
...
@@ -456,6 +456,7 @@ require('./task_list');
...
@@ -456,6 +456,7 @@ require('./task_list');
form
.
find
(
"
#note_line_code
"
).
remove
();
form
.
find
(
"
#note_line_code
"
).
remove
();
form
.
find
(
"
#note_position
"
).
remove
();
form
.
find
(
"
#note_position
"
).
remove
();
form
.
find
(
"
#note_type
"
).
remove
();
form
.
find
(
"
#note_type
"
).
remove
();
form
.
find
(
"
#note_in_reply_to_discussion_id
"
).
remove
();
form
.
find
(
'
.js-comment-resolve-button
'
).
closest
(
'
comment-and-resolve-btn
'
).
remove
();
form
.
find
(
'
.js-comment-resolve-button
'
).
closest
(
'
comment-and-resolve-btn
'
).
remove
();
return
this
.
parentTimeline
=
form
.
parents
(
'
.timeline
'
);
return
this
.
parentTimeline
=
form
.
parents
(
'
.timeline
'
);
};
};
...
@@ -470,10 +471,24 @@ require('./task_list');
...
@@ -470,10 +471,24 @@ require('./task_list');
*/
*/
Notes
.
prototype
.
setupNoteForm
=
function
(
form
)
{
Notes
.
prototype
.
setupNoteForm
=
function
(
form
)
{
var
textarea
;
var
textarea
,
key
;
new
gl
.
GLForm
(
form
);
new
gl
.
GLForm
(
form
);
textarea
=
form
.
find
(
"
.js-note-text
"
);
textarea
=
form
.
find
(
"
.js-note-text
"
);
return
new
Autosave
(
textarea
,
[
"
Note
"
,
form
.
find
(
"
#note_noteable_type
"
).
val
(),
form
.
find
(
"
#note_noteable_id
"
).
val
(),
form
.
find
(
"
#note_commit_id
"
).
val
(),
form
.
find
(
"
#note_type
"
).
val
(),
form
.
find
(
"
#note_line_code
"
).
val
(),
form
.
find
(
"
#note_position
"
).
val
()]);
key
=
[
"
Note
"
,
form
.
find
(
"
#note_noteable_type
"
).
val
(),
form
.
find
(
"
#note_noteable_id
"
).
val
(),
form
.
find
(
"
#note_commit_id
"
).
val
(),
form
.
find
(
"
#note_type
"
).
val
(),
form
.
find
(
"
#in_reply_to_discussion_id
"
).
val
(),
// LegacyDiffNote
form
.
find
(
"
#note_line_code
"
).
val
(),
// DiffNote
form
.
find
(
"
#note_position
"
).
val
()
];
return
new
Autosave
(
textarea
,
key
);
};
};
/*
/*
...
@@ -510,7 +525,7 @@ require('./task_list');
...
@@ -510,7 +525,7 @@ require('./task_list');
}
}
}
}
this
.
render
Discussion
Note
(
note
);
this
.
renderNote
(
note
);
// cleanup after successfully creating a diff/discussion note
// cleanup after successfully creating a diff/discussion note
this
.
removeDiscussionNoteForm
(
$form
);
this
.
removeDiscussionNoteForm
(
$form
);
};
};
...
@@ -727,23 +742,35 @@ require('./task_list');
...
@@ -727,23 +742,35 @@ require('./task_list');
Sets some hidden fields in the form.
Sets some hidden fields in the form.
Note: dataHolder must have the "discussionId", "lineCode", "noteableType"
Note: dataHolder must have the "discussionId" and "lineCode" data attributes set.
and "noteableId" data attributes set.
*/
*/
Notes
.
prototype
.
setupDiscussionNoteForm
=
function
(
dataHolder
,
form
)
{
Notes
.
prototype
.
setupDiscussionNoteForm
=
function
(
dataHolder
,
form
)
{
// setup note target
// setup note target
form
.
attr
(
'
id
'
,
"
new-discussion-note-form-
"
+
(
dataHolder
.
data
(
"
discussionId
"
)));
var
discussionID
=
dataHolder
.
data
(
"
discussionId
"
);
form
.
attr
(
'
id
'
,
"
new-discussion-note-form-
"
+
discussionID
);
form
.
attr
(
"
data-discussion-id
"
,
discussionID
);
form
.
attr
(
"
data-original-discussion-id
"
,
dataHolder
.
data
(
"
originalDiscussionId
"
)
||
discussionID
);
form
.
attr
(
"
data-line-code
"
,
dataHolder
.
data
(
"
lineCode
"
));
form
.
attr
(
"
data-line-code
"
,
dataHolder
.
data
(
"
lineCode
"
));
form
.
find
(
"
#note_type
"
).
val
(
dataHolder
.
data
(
"
noteType
"
));
form
.
find
(
"
#line_type
"
).
val
(
dataHolder
.
data
(
"
lineType
"
));
form
.
find
(
"
#line_type
"
).
val
(
dataHolder
.
data
(
"
lineType
"
));
form
.
find
(
"
#in_reply_to_discussion_id
"
).
val
(
dataHolder
.
data
(
"
originalDiscussionId
"
));
form
.
find
(
"
#note_noteable_type
"
).
val
(
dataHolder
.
data
(
"
noteableType
"
));
form
.
find
(
"
#note_noteable_id
"
).
val
(
dataHolder
.
data
(
"
noteableId
"
));
form
.
find
(
"
#note_commit_id
"
).
val
(
dataHolder
.
data
(
"
commitId
"
));
form
.
find
(
"
#note_commit_id
"
).
val
(
dataHolder
.
data
(
"
commitId
"
));
form
.
find
(
"
#note_type
"
).
val
(
dataHolder
.
data
(
"
noteType
"
));
// LegacyDiffNote
form
.
find
(
"
#note_line_code
"
).
val
(
dataHolder
.
data
(
"
lineCode
"
));
form
.
find
(
"
#note_line_code
"
).
val
(
dataHolder
.
data
(
"
lineCode
"
));
// DiffNote
form
.
find
(
"
#note_position
"
).
val
(
dataHolder
.
attr
(
"
data-position
"
));
form
.
find
(
"
#note_position
"
).
val
(
dataHolder
.
attr
(
"
data-position
"
));
form
.
find
(
"
#note_noteable_type
"
).
val
(
dataHolder
.
data
(
"
noteableType
"
));
form
.
find
(
"
#note_noteable_id
"
).
val
(
dataHolder
.
data
(
"
noteableId
"
));
form
.
find
(
'
.js-note-discard
'
).
show
().
removeClass
(
'
js-note-discard
'
).
addClass
(
'
js-close-discussion-note-form
'
).
text
(
form
.
find
(
'
.js-close-discussion-note-form
'
).
data
(
'
cancel-text
'
));
form
.
find
(
'
.js-note-discard
'
).
show
().
removeClass
(
'
js-note-discard
'
).
addClass
(
'
js-close-discussion-note-form
'
).
text
(
form
.
find
(
'
.js-close-discussion-note-form
'
).
data
(
'
cancel-text
'
));
form
.
find
(
'
.js-note-target-close
'
).
remove
();
form
.
find
(
'
.js-note-target-close
'
).
remove
();
form
.
find
(
'
.js-note-new-discussion
'
).
remove
();
this
.
setupNoteForm
(
form
);
this
.
setupNoteForm
(
form
);
if
(
typeof
gl
.
diffNotesCompileComponents
!==
'
undefined
'
)
{
if
(
typeof
gl
.
diffNotesCompileComponents
!==
'
undefined
'
)
{
...
...
app/controllers/projects/commit_controller.rb
View file @
08bbb9fc
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
#
#
# Not to be confused with CommitsController, plural.
# Not to be confused with CommitsController, plural.
class
Projects::CommitController
<
Projects
::
ApplicationController
class
Projects::CommitController
<
Projects
::
ApplicationController
include
NotesHelper
include
CreatesCommit
include
CreatesCommit
include
DiffForPath
include
DiffForPath
include
DiffHelper
include
DiffHelper
...
@@ -111,22 +112,19 @@ class Projects::CommitController < Projects::ApplicationController
...
@@ -111,22 +112,19 @@ class Projects::CommitController < Projects::ApplicationController
end
end
def
define_note_vars
def
define_note_vars
@grouped_diff_discussions
=
commit
.
notes
.
grouped_diff_discussions
@noteable
=
@commit
@notes
=
commit
.
notes
.
non_diff_notes
.
fresh
Banzai
::
NoteRenderer
.
render
(
@grouped_diff_discussions
.
values
.
flat_map
(
&
:notes
)
+
@notes
,
@project
,
current_user
,
)
@note
=
@project
.
build_commit_note
(
commit
)
@note
=
@project
.
build_commit_note
(
commit
)
@noteable
=
@commit
@new_diff_note_attrs
=
{
@comments_target
=
{
noteable_type:
'Commit'
,
noteable_type:
'Commit'
,
commit_id:
@commit
.
id
commit_id:
@commit
.
id
}
}
@grouped_diff_discussions
=
commit
.
grouped_diff_discussions
@discussions
=
commit
.
discussions
@notes
=
(
@grouped_diff_discussions
.
values
+
@discussions
).
flat_map
(
&
:notes
)
@notes
=
prepare_notes_for_rendering
(
@notes
)
end
end
def
assign_change_commit_vars
def
assign_change_commit_vars
...
...
app/controllers/projects/discussions_controller.rb
View file @
08bbb9fc
...
@@ -28,7 +28,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
...
@@ -28,7 +28,7 @@ class Projects::DiscussionsController < Projects::ApplicationController
end
end
def
discussion
def
discussion
@discussion
||=
@merge_request
.
find_di
ff_di
scussion
(
params
[
:id
])
||
render_404
@discussion
||=
@merge_request
.
find_discussion
(
params
[
:id
])
||
render_404
end
end
def
authorize_resolve_discussion!
def
authorize_resolve_discussion!
...
...
app/controllers/projects/issues_controller.rb
View file @
08bbb9fc
...
@@ -84,15 +84,11 @@ class Projects::IssuesController < Projects::ApplicationController
...
@@ -84,15 +84,11 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
def
show
def
show
raw_notes
=
@issue
.
notes
.
inc_relations_for_view
.
fresh
@notes
=
Banzai
::
NoteRenderer
.
render
(
raw_notes
,
@project
,
current_user
,
@path
,
@project_wiki
,
@ref
)
@note
=
@project
.
notes
.
new
(
noteable:
@issue
)
@noteable
=
@issue
@noteable
=
@issue
@note
=
@project
.
notes
.
new
(
noteable:
@issue
)
preload_max_access_for_authors
(
@notes
,
@project
)
@discussions
=
@issue
.
discussions
@notes
=
prepare_notes_for_rendering
(
@discussions
.
flat_map
(
&
:notes
))
respond_to
do
|
format
|
respond_to
do
|
format
|
format
.
html
format
.
html
...
...
app/controllers/projects/merge_requests_controller.rb
View file @
08bbb9fc
...
@@ -570,20 +570,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
...
@@ -570,20 +570,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@note
=
@project
.
notes
.
new
(
noteable:
@merge_request
)
@note
=
@project
.
notes
.
new
(
noteable:
@merge_request
)
@discussions
=
@merge_request
.
discussions
@discussions
=
@merge_request
.
discussions
@notes
=
prepare_notes_for_rendering
(
@discussions
.
flat_map
(
&
:notes
))
preload_noteable_for_regular_notes
(
@discussions
.
flat_map
(
&
:notes
))
# This is not executed lazily
@notes
=
Banzai
::
NoteRenderer
.
render
(
@discussions
.
flat_map
(
&
:notes
),
@project
,
current_user
,
@path
,
@project_wiki
,
@ref
)
preload_max_access_for_authors
(
@notes
,
@project
)
end
end
def
define_widget_vars
def
define_widget_vars
...
@@ -596,22 +583,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
...
@@ -596,22 +583,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
def
define_diff_comment_vars
def
define_diff_comment_vars
@
comments_target
=
{
@
new_diff_note_attrs
=
{
noteable_type:
'MergeRequest'
,
noteable_type:
'MergeRequest'
,
noteable_id:
@merge_request
.
id
noteable_id:
@merge_request
.
id
}
}
@use_legacy_diff_notes
=
!
@merge_request
.
has_complete_diff_refs?
@use_legacy_diff_notes
=
!
@merge_request
.
has_complete_diff_refs?
@grouped_diff_discussions
=
@merge_request
.
notes
.
inc_relations_for_view
.
grouped_diff_discussions
@grouped_diff_discussions
=
@merge_request
.
grouped_diff_discussions
Banzai
::
NoteRenderer
.
render
(
@notes
=
prepare_notes_for_rendering
(
@grouped_diff_discussions
.
values
.
flat_map
(
&
:notes
))
@grouped_diff_discussions
.
values
.
flat_map
(
&
:notes
),
@project
,
current_user
,
@path
,
@project_wiki
,
@ref
)
end
end
def
define_pipelines_vars
def
define_pipelines_vars
...
...
app/controllers/projects/notes_controller.rb
View file @
08bbb9fc
...
@@ -6,13 +6,14 @@ class Projects::NotesController < Projects::ApplicationController
...
@@ -6,13 +6,14 @@ class Projects::NotesController < Projects::ApplicationController
before_action
:authorize_create_note!
,
only:
[
:create
]
before_action
:authorize_create_note!
,
only:
[
:create
]
before_action
:authorize_admin_note!
,
only:
[
:update
,
:destroy
]
before_action
:authorize_admin_note!
,
only:
[
:update
,
:destroy
]
before_action
:authorize_resolve_note!
,
only:
[
:resolve
,
:unresolve
]
before_action
:authorize_resolve_note!
,
only:
[
:resolve
,
:unresolve
]
before_action
:find_current_user_notes
,
only:
[
:index
]
def
index
def
index
current_fetched_at
=
Time
.
now
.
to_i
current_fetched_at
=
Time
.
now
.
to_i
notes_json
=
{
notes:
[],
last_fetched_at:
current_fetched_at
}
notes_json
=
{
notes:
[],
last_fetched_at:
current_fetched_at
}
@notes
=
notes_finder
.
execute
.
inc_author
@notes
.
each
do
|
note
|
@notes
.
each
do
|
note
|
next
if
note
.
cross_reference_not_visible_for?
(
current_user
)
next
if
note
.
cross_reference_not_visible_for?
(
current_user
)
...
@@ -23,7 +24,11 @@ class Projects::NotesController < Projects::ApplicationController
...
@@ -23,7 +24,11 @@ class Projects::NotesController < Projects::ApplicationController
end
end
def
create
def
create
create_params
=
note_params
.
merge
(
merge_request_diff_head_sha:
params
[
:merge_request_diff_head_sha
])
create_params
=
note_params
.
merge
(
merge_request_diff_head_sha:
params
[
:merge_request_diff_head_sha
],
in_reply_to_discussion_id:
params
[
:in_reply_to_discussion_id
],
new_discussion:
params
[
:new_discussion
],
)
@note
=
Notes
::
CreateService
.
new
(
project
,
current_user
,
create_params
).
execute
@note
=
Notes
::
CreateService
.
new
(
project
,
current_user
,
create_params
).
execute
if
@note
.
is_a?
(
Note
)
if
@note
.
is_a?
(
Note
)
...
@@ -111,6 +116,17 @@ class Projects::NotesController < Projects::ApplicationController
...
@@ -111,6 +116,17 @@ class Projects::NotesController < Projects::ApplicationController
)
)
end
end
def
discussion_html
(
discussion
)
return
if
discussion
.
render_as_individual_notes?
render_to_string
(
"discussions/_discussion"
,
layout:
false
,
formats:
[
:html
],
locals:
{
discussion:
discussion
}
)
end
def
diff_discussion_html
(
discussion
)
def
diff_discussion_html
(
discussion
)
return
unless
discussion
.
diff_discussion?
return
unless
discussion
.
diff_discussion?
...
@@ -135,17 +151,6 @@ class Projects::NotesController < Projects::ApplicationController
...
@@ -135,17 +151,6 @@ class Projects::NotesController < Projects::ApplicationController
)
)
end
end
def
discussion_html
(
discussion
)
return
unless
discussion
.
diff_discussion?
render_to_string
(
"discussions/_discussion"
,
layout:
false
,
formats:
[
:html
],
locals:
{
discussion:
discussion
}
)
end
def
note_json
(
note
)
def
note_json
(
note
)
attrs
=
{
attrs
=
{
id:
note
.
id
id:
note
.
id
...
@@ -156,33 +161,22 @@ class Projects::NotesController < Projects::ApplicationController
...
@@ -156,33 +161,22 @@ class Projects::NotesController < Projects::ApplicationController
attrs
.
merge!
(
attrs
.
merge!
(
valid:
true
,
valid:
true
,
discussion_id:
note
.
discussion_id
,
discussion_id:
note
.
discussion_id
(
noteable
)
,
html:
note_html
(
note
),
html:
note_html
(
note
),
note:
note
.
note
note:
note
.
note
)
)
if
note
.
diff_note?
discussion
=
note
.
to_discussion
(
noteable
)
discussion
=
note
.
to_discussion
unless
discussion
.
render_as_individual_notes?
attrs
.
merge!
(
attrs
.
merge!
(
diff_discussion_html:
diff_discussion_html
(
discussion
),
diff_discussion_html:
diff_discussion_html
(
discussion
),
discussion_html:
discussion_html
(
discussion
)
discussion_html:
discussion_html
(
discussion
),
)
# The discussion_id is used to add the comment to the correct discussion
# Since the `discussion_id` can change, for example when new commits are pushed into an MR,
# element on the merge request page. Among other things, the discussion_id
# the never-changing `original_discussion_id` is used as a fallback to the find the relevant
# contains the sha of head commit of the merge request.
# discussion container to add this note to.
# When new commits are pushed into the merge request after the initial
original_discussion_id:
note
.
original_discussion_id
# load of the merge request page, the discussion elements will still have
)
# the old discussion_ids, with the old head commit sha. The new comment,
# however, will have the new discussion_id with the new commit sha.
# To ensure that these new comments will still end up in the correct
# discussion element, we also send the original discussion_id, with the
# old commit sha, along, and fall back on this value when no discussion
# element with the new discussion_id could be found.
if
note
.
new_diff_note?
&&
note
.
position
!=
note
.
original_position
attrs
[
:original_discussion_id
]
=
note
.
original_discussion_id
end
end
end
else
else
attrs
.
merge!
(
attrs
.
merge!
(
...
@@ -205,14 +199,30 @@ class Projects::NotesController < Projects::ApplicationController
...
@@ -205,14 +199,30 @@ class Projects::NotesController < Projects::ApplicationController
def
note_params
def
note_params
params
.
require
(
:note
).
permit
(
params
.
require
(
:note
).
permit
(
:note
,
:noteable
,
:noteable_id
,
:noteable_type
,
:project_id
,
:project_id
,
:attachment
,
:line_code
,
:commit_id
,
:type
,
:position
:noteable_type
,
:noteable_id
,
:commit_id
,
:noteable
,
:type
,
:note
,
:attachment
,
# LegacyDiffNote
:line_code
,
# DiffNote
:position
)
)
end
end
def
find_current_user_notes
def
notes_finder
@notes
=
NotesFinder
.
new
(
project
,
current_user
,
params
.
merge
(
last_fetched_at:
last_fetched_at
))
@notes_finder
||=
NotesFinder
.
new
(
project
,
current_user
,
params
.
merge
(
last_fetched_at:
last_fetched_at
))
.
execute
.
inc_author
end
def
noteable
@noteable
||=
notes_finder
.
target
end
end
def
last_fetched_at
def
last_fetched_at
...
...
app/controllers/projects/snippets_controller.rb
View file @
08bbb9fc
class
Projects::SnippetsController
<
Projects
::
ApplicationController
class
Projects::SnippetsController
<
Projects
::
ApplicationController
include
NotesHelper
include
ToggleAwardEmoji
include
ToggleAwardEmoji
include
SpammableActions
include
SpammableActions
include
SnippetsActions
include
SnippetsActions
...
@@ -55,8 +56,10 @@ class Projects::SnippetsController < Projects::ApplicationController
...
@@ -55,8 +56,10 @@ class Projects::SnippetsController < Projects::ApplicationController
def
show
def
show
@note
=
@project
.
notes
.
new
(
noteable:
@snippet
)
@note
=
@project
.
notes
.
new
(
noteable:
@snippet
)
@notes
=
Banzai
::
NoteRenderer
.
render
(
@snippet
.
notes
.
fresh
,
@project
,
current_user
)
@noteable
=
@snippet
@noteable
=
@snippet
@discussions
=
@snippet
.
discussions
@notes
=
prepare_notes_for_rendering
(
@discussions
.
flat_map
(
&
:notes
))
end
end
def
destroy
def
destroy
...
...
app/finders/notes_finder.rb
View file @
08bbb9fc
...
@@ -17,29 +17,46 @@ class NotesFinder
...
@@ -17,29 +17,46 @@ class NotesFinder
@project
=
project
@project
=
project
@current_user
=
current_user
@current_user
=
current_user
@params
=
params
@params
=
params
init_collection
end
end
def
execute
def
execute
@notes
=
since_fetch_at
(
@params
[
:last_fetched_at
])
if
@params
[
:last_fetched_at
]
@notes
=
init_collection
@notes
=
since_fetch_at
(
@params
[
:last_fetched_at
],
@notes
)
if
@params
[
:last_fetched_at
]
@notes
@notes
end
end
private
def
target
return
@target
if
defined?
(
@target
)
def
init_collection
target_type
=
@params
[
:target_type
]
@notes
=
target_id
=
@params
[
:target_id
]
if
@params
[
:target_id
]
on_target
(
@params
[
:target_type
],
@params
[
:target_id
])
return
@target
=
nil
unless
target_type
&&
target_id
@target
=
if
target_type
==
"commit"
if
Ability
.
allowed?
(
@current_user
,
:download_code
,
@project
)
@project
.
commit
(
target_id
)
end
else
else
note
s_of_any_type
note
ables_for_type
(
target_type
).
find
(
target_id
)
end
end
end
end
private
def
init_collection
if
target
notes_on_target
else
notes_of_any_type
end
end
def
notes_of_any_type
def
notes_of_any_type
types
=
%w(commit issue merge_request snippet)
types
=
%w(commit issue merge_request snippet)
note_relations
=
types
.
map
{
|
t
|
notes_for_type
(
t
)
}
note_relations
=
types
.
map
{
|
t
|
notes_for_type
(
t
)
}
note_relations
.
map!
{
|
notes
|
search
(
@params
[
:search
],
notes
)
}
if
@params
[
:search
]
note_relations
.
map!
{
|
notes
|
search
(
@params
[
:search
],
notes
)
}
if
@params
[
:search
]
UnionFinder
.
new
.
find_union
(
note_relations
,
Note
)
UnionFinder
.
new
.
find_union
(
note_relations
,
Note
)
end
end
...
@@ -69,17 +86,11 @@ class NotesFinder
...
@@ -69,17 +86,11 @@ class NotesFinder
end
end
end
end
def
on_target
(
target_type
,
target_id
)
def
notes_on_target
if
target
_type
==
"commit"
if
target
.
respond_to?
(
:related_notes
)
notes_for_type
(
'commit'
).
for_commit_id
(
target_id
)
target
.
related_notes
else
else
target
=
noteables_for_type
(
target_type
).
find
(
target_id
)
target
.
notes
if
target
.
respond_to?
(
:related_notes
)
target
.
related_notes
else
target
.
notes
end
end
end
end
end
...
@@ -94,10 +105,9 @@ class NotesFinder
...
@@ -94,10 +105,9 @@ class NotesFinder
# Notes changed since last fetch
# Notes changed since last fetch
# Uses overlapping intervals to avoid worrying about race conditions
# Uses overlapping intervals to avoid worrying about race conditions
def
since_fetch_at
(
fetch_time
)
def
since_fetch_at
(
fetch_time
,
notes_relation
=
@notes
)
# Default to 0 to remain compatible with old clients
# Default to 0 to remain compatible with old clients
last_fetched_at
=
Time
.
at
(
@params
.
fetch
(
:last_fetched_at
,
0
).
to_i
)
last_fetched_at
=
Time
.
at
(
@params
.
fetch
(
:last_fetched_at
,
0
).
to_i
)
notes_relation
.
where
(
'updated_at > ?'
,
last_fetched_at
-
FETCH_OVERLAP
).
fresh
@notes
.
where
(
'updated_at > ?'
,
last_fetched_at
-
FETCH_OVERLAP
).
fresh
end
end
end
end
app/helpers/notes_helper.rb
View file @
08bbb9fc
...
@@ -24,9 +24,9 @@ module NotesHelper
...
@@ -24,9 +24,9 @@ module NotesHelper
end
end
def
diff_view_data
def
diff_view_data
return
{}
unless
@
comments_target
return
{}
unless
@
new_diff_note_attrs
@
comments_target
.
slice
(
:noteable_id
,
:noteable_type
,
:commit_id
)
@
new_diff_note_attrs
.
slice
(
:noteable_id
,
:noteable_type
,
:commit_id
)
end
end
def
diff_view_line_data
(
line_code
,
position
,
line_type
)
def
diff_view_line_data
(
line_code
,
position
,
line_type
)
...
@@ -53,37 +53,26 @@ module NotesHelper
...
@@ -53,37 +53,26 @@ module NotesHelper
}
}
if
use_legacy_diff_note
if
use_legacy_diff_note
discussion_id
=
LegacyDiffNote
.
discussion_id
(
new_note
=
LegacyDiffNote
.
new
(
@new_diff_note_attrs
.
merge
(
line_code:
line_code
))
@comments_target
[
:noteable_type
],
discussion_id
=
new_note
.
discussion_id
@comments_target
[
:noteable_id
]
||
@comments_target
[
:commit_id
],
line_code
)
data
.
merge!
(
note_type:
LegacyDiffNote
.
name
,
discussion_id:
discussion_id
)
else
else
discussion_id
=
DiffNote
.
discussion_id
(
new_note
=
DiffNote
.
new
(
@new_diff_note_attrs
.
merge
(
position:
position
))
@comments_target
[
:noteable_type
],
discussion_id
=
new_note
.
discussion_id
@comments_target
[
:noteable_id
]
||
@comments_target
[
:commit_id
],
position
data
[
:position
]
=
position
.
to_json
)
data
.
merge!
(
position:
position
.
to_json
,
note_type:
DiffNote
.
name
,
discussion_id:
discussion_id
)
end
end
data
data
.
merge
(
note_type:
new_note
.
type
,
discussion_id:
discussion_id
)
end
end
def
link_to_reply_discussion
(
discussion
,
line_type
=
nil
)
def
link_to_reply_discussion
(
discussion
,
line_type
=
nil
)
return
unless
current_user
return
unless
current_user
data
=
discussion
.
reply_attributes
.
merge
(
line_type:
line_type
)
data
=
{
discussion_id:
discussion
.
id
,
original_discussion_id:
discussion
.
original_id
,
line_type:
line_type
}
data
[
:line_code
]
=
discussion
.
line_code
if
discussion
.
respond_to?
(
:line_code
)
button_tag
'Reply...'
,
class:
'btn btn-text-field js-discussion-reply-button'
,
button_tag
'Reply...'
,
class:
'btn btn-text-field js-discussion-reply-button'
,
data:
data
,
title:
'Add a reply'
data:
data
,
title:
'Add a reply'
...
@@ -95,7 +84,15 @@ module NotesHelper
...
@@ -95,7 +84,15 @@ module NotesHelper
end
end
def
preload_noteable_for_regular_notes
(
notes
)
def
preload_noteable_for_regular_notes
(
notes
)
ActiveRecord
::
Associations
::
Preloader
.
new
.
preload
(
notes
.
select
{
|
note
|
!
note
.
for_commit?
},
:noteable
)
ActiveRecord
::
Associations
::
Preloader
.
new
.
preload
(
notes
.
reject
(
&
:for_commit?
),
:noteable
)
end
def
prepare_notes_for_rendering
(
notes
)
preload_noteable_for_regular_notes
(
notes
)
preload_max_access_for_authors
(
notes
,
@project
)
Banzai
::
NoteRenderer
.
render
(
notes
,
@project
,
current_user
)
notes
end
end
def
note_max_access_for_user
(
note
)
def
note_max_access_for_user
(
note
)
...
...
app/models/commit.rb
View file @
08bbb9fc
...
@@ -2,6 +2,7 @@ class Commit
...
@@ -2,6 +2,7 @@ class Commit
extend
ActiveModel
::
Naming
extend
ActiveModel
::
Naming
include
ActiveModel
::
Conversion
include
ActiveModel
::
Conversion
include
Noteable
include
Participable
include
Participable
include
Mentionable
include
Mentionable
include
Referable
include
Referable
...
@@ -203,6 +204,10 @@ class Commit
...
@@ -203,6 +204,10 @@ class Commit
project
.
notes
.
for_commit_id
(
self
.
id
)
project
.
notes
.
for_commit_id
(
self
.
id
)
end
end
def
discussion_notes
notes
.
non_diff_notes
end
def
notes_with_associations
def
notes_with_associations
notes
.
includes
(
:author
)
notes
.
includes
(
:author
)
end
end
...
...
app/models/commit_discussion.rb
0 → 100644
View file @
08bbb9fc
class
CommitDiscussion
<
Discussion
def
self
.
override_discussion_id
(
note
)
discussion_id
(
note
)
end
def
potentially_resolvable?
false
end
end
app/models/concerns/note_on_diff.rb
View file @
08bbb9fc
...
@@ -24,12 +24,4 @@ module NoteOnDiff
...
@@ -24,12 +24,4 @@ module NoteOnDiff
def
diff_attributes
def
diff_attributes
raise
NotImplementedError
raise
NotImplementedError
end
end
def
can_be_award_emoji?
false
end
def
to_discussion
Discussion
.
new
([
self
])
end
end
end
app/models/concerns/noteable.rb
0 → 100644
View file @
08bbb9fc
module
Noteable
def
discussion_notes
notes
end
delegate
:find_discussion
,
:find_original_discussion
,
to: :discussion_notes
def
discussions
@discussions
||=
discussion_notes
.
inc_relations_for_view
.
discussions
(
self
)
end
def
grouped_diff_discussions
notes
.
inc_relations_for_view
.
grouped_diff_discussions
end
end
app/models/concerns/resolvable_note.rb
0 → 100644
View file @
08bbb9fc
module
ResolvableNote
extend
ActiveSupport
::
Concern
included
do
belongs_to
:resolved_by
,
class_name:
"User"
validates
:resolved_by
,
presence:
true
,
if: :resolved?
# Keep this scope in sync with the logic in `#resolvable?` in `Note` subclasses that are resolvable
scope
:resolvable
,
->
{
where
(
type:
%w(DiffNote DiscussionNote)
).
user
.
where
(
noteable_type:
'MergeRequest'
)
}
scope
:resolved
,
->
{
resolvable
.
where
.
not
(
resolved_at:
nil
)
}
scope
:unresolved
,
->
{
resolvable
.
where
(
resolved_at:
nil
)
}
end
module
ClassMethods
# This method must be kept in sync with `#resolve!`
def
resolve!
(
current_user
)
unresolved
.
update_all
(
resolved_at:
Time
.
now
,
resolved_by_id:
current_user
.
id
)
end
# This method must be kept in sync with `#unresolve!`
def
unresolve!
resolved
.
update_all
(
resolved_at:
nil
,
resolved_by_id:
nil
)
end
end
# If you update this method remember to also update the scope `resolvable`
def
resolvable?
to_discussion
.
potentially_resolvable?
&&
!
system
?
end
def
resolved?
return
false
unless
resolvable?
self
.
resolved_at
.
present?
end
def
to_be_resolved?
resolvable?
&&
!
resolved?
end
# If you update this method remember to also update `.resolve!`
def
resolve!
(
current_user
)
return
unless
resolvable?
return
if
resolved?
self
.
resolved_at
=
Time
.
now
self
.
resolved_by
=
current_user
save!
end
# If you update this method remember to also update `.unresolve!`
def
unresolve!
return
unless
resolvable?
return
unless
resolved?
self
.
resolved_at
=
nil
self
.
resolved_by
=
nil
save!
end
end
app/models/diff_discussion.rb
0 → 100644
View file @
08bbb9fc
class
DiffDiscussion
<
Discussion
NUMBER_OF_TRUNCATED_DIFF_LINES
=
16
delegate
:line_code
,
:original_line_code
,
:diff_file
,
:for_line?
,
:active?
,
to: :first_note
delegate
:blob
,
:highlighted_diff_lines
,
:diff_lines
,
to: :diff_file
,
allow_nil:
true
def
self
.
build_discussion_id
(
note
)
[
*
super
(
note
),
*
unique_position_identifier
(
note
)]
end
def
self
.
build_original_discussion_id
(
note
)
[
*
Discussion
.
build_discussion_id
(
note
),
*
note
.
original_position
.
key
]
end
def
self
.
unique_position_identifier
(
note
)
note
.
position
.
key
end
def
diff_discussion?
true
end
def
legacy_diff_discussion?
false
end
def
active?
return
@active
if
@active
.
present?
@active
=
first_note
.
active?
end
MEMOIZED_VALUES
<<
:active
def
reply_attributes
super
.
merge
(
first_note
.
diff_attributes
)
end
# Returns an array of at most 16 highlighted lines above a diff note
def
truncated_diff_lines
(
highlight:
true
)
lines
=
highlight
?
highlighted_diff_lines
:
diff_lines
prev_lines
=
[]
lines
.
each
do
|
line
|
if
line
.
meta?
prev_lines
.
clear
else
prev_lines
<<
line
break
if
for_line?
(
line
)
prev_lines
.
shift
if
prev_lines
.
length
>=
NUMBER_OF_TRUNCATED_DIFF_LINES
end
end
prev_lines
end
end
app/models/diff_note.rb
View file @
08bbb9fc
...
@@ -9,58 +9,44 @@ class DiffNote < Note
...
@@ -9,58 +9,44 @@ class DiffNote < Note
validates
:diff_line
,
presence:
true
validates
:diff_line
,
presence:
true
validates
:line_code
,
presence:
true
,
line_code:
true
validates
:line_code
,
presence:
true
,
line_code:
true
validates
:noteable_type
,
inclusion:
{
in:
%w(Commit MergeRequest)
}
validates
:noteable_type
,
inclusion:
{
in:
%w(Commit MergeRequest)
}
validates
:resolved_by
,
presence:
true
,
if: :resolved?
validate
:positions_complete
validate
:positions_complete
validate
:verify_supported
validate
:verify_supported
# Keep this scope in sync with the logic in `#resolvable?`
scope
:resolvable
,
->
{
user
.
where
(
noteable_type:
'MergeRequest'
)
}
scope
:resolved
,
->
{
resolvable
.
where
.
not
(
resolved_at:
nil
)
}
scope
:unresolved
,
->
{
resolvable
.
where
(
resolved_at:
nil
)
}
after_initialize
:ensure_original_discussion_id
before_validation
:set_original_position
,
:update_position
,
on: :create
before_validation
:set_original_position
,
:update_position
,
on: :create
before_validation
:set_line_code
,
:set_original_discussion_id
before_validation
:set_line_code
# We need to do this again, because it's already in `Note`, but is affected by
# We need to do this again, because it's already in `Note`, but is affected by
# `update_position` and needs to run after that.
# `update_position` and needs to run after that.
before_validation
:set_discussion_id
before_validation
:set_discussion_id
,
if: :position_changed?
after_save
:keep_around_commits
after_save
:keep_around_commits
class
<<
self
def
build_discussion_id
(
noteable_type
,
noteable_id
,
position
)
[
super
(
noteable_type
,
noteable_id
),
*
position
.
key
].
join
(
"-"
)
end
# This method must be kept in sync with `#resolve!`
def
resolve!
(
current_user
)
unresolved
.
update_all
(
resolved_at:
Time
.
now
,
resolved_by_id:
current_user
.
id
)
end
# This method must be kept in sync with `#unresolve!`
def
unresolve!
resolved
.
update_all
(
resolved_at:
nil
,
resolved_by_id:
nil
)
end
end
def
new_diff_note?
def
new_diff_note?
true
true
end
end
def
discussion_class
(
*
)
DiffDiscussion
end
def
diff_attributes
def
diff_attributes
{
position:
position
.
to_json
}
{
original_position:
original_position
.
to_json
,
position:
position
.
to_json
,
}
end
end
def
position
=
(
new_position
)
%i(original_position= position=)
.
each
do
|
meth
|
if
new_position
.
is_a?
(
String
)
define_method
meth
do
|
new_position
|
new_position
=
JSON
.
parse
(
new_position
)
rescue
nil
if
new_position
.
is_a?
(
String
)
end
new_position
=
JSON
.
parse
(
new_position
)
rescue
nil
end
if
new_position
.
is_a?
(
Hash
)
if
new_position
.
is_a?
(
Hash
)
new_position
=
new_position
.
with_indifferent_access
new_position
=
new_position
.
with_indifferent_access
new_position
=
Gitlab
::
Diff
::
Position
.
new
(
new_position
)
new_position
=
Gitlab
::
Diff
::
Position
.
new
(
new_position
)
end
end
super
(
new_position
)
super
(
new_position
)
end
end
end
def
diff_file
def
diff_file
...
@@ -88,43 +74,6 @@ class DiffNote < Note
...
@@ -88,43 +74,6 @@ class DiffNote < Note
self
.
position
.
diff_refs
==
diff_refs
self
.
position
.
diff_refs
==
diff_refs
end
end
# If you update this method remember to also update the scope `resolvable`
def
resolvable?
!
system
?
&&
for_merge_request?
end
def
resolved?
return
false
unless
resolvable?
self
.
resolved_at
.
present?
end
# If you update this method remember to also update `.resolve!`
def
resolve!
(
current_user
)
return
unless
resolvable?
return
if
resolved?
self
.
resolved_at
=
Time
.
now
self
.
resolved_by
=
current_user
save!
end
# If you update this method remember to also update `.unresolve!`
def
unresolve!
return
unless
resolvable?
return
unless
resolved?
self
.
resolved_at
=
nil
self
.
resolved_by
=
nil
save!
end
def
discussion
return
unless
resolvable?
self
.
noteable
.
find_diff_discussion
(
self
.
discussion_id
)
end
private
private
def
supported?
def
supported?
...
@@ -140,33 +89,13 @@ class DiffNote < Note
...
@@ -140,33 +89,13 @@ class DiffNote < Note
end
end
def
set_original_position
def
set_original_position
self
.
original_position
=
self
.
position
.
dup
self
.
original_position
=
self
.
position
.
dup
unless
self
.
original_position
&
.
complete?
end
end
def
set_line_code
def
set_line_code
self
.
line_code
=
self
.
position
.
line_code
(
self
.
project
.
repository
)
self
.
line_code
=
self
.
position
.
line_code
(
self
.
project
.
repository
)
end
end
def
ensure_original_discussion_id
return
unless
self
.
persisted?
return
if
self
.
original_discussion_id
set_original_discussion_id
update_column
(
:original_discussion_id
,
self
.
original_discussion_id
)
end
def
set_original_discussion_id
self
.
original_discussion_id
=
Digest
::
SHA1
.
hexdigest
(
build_original_discussion_id
)
end
def
build_discussion_id
self
.
class
.
build_discussion_id
(
noteable_type
,
noteable_id
||
commit_id
,
position
)
end
def
build_original_discussion_id
self
.
class
.
build_discussion_id
(
noteable_type
,
noteable_id
||
commit_id
,
original_position
)
end
def
update_position
def
update_position
return
unless
supported?
return
unless
supported?
return
if
for_commit?
return
if
for_commit?
...
...
app/models/discussion.rb
View file @
08bbb9fc
class
Discussion
class
Discussion
NUMBER_OF_TRUNCATED_DIFF_LINES
=
16
MEMOIZED_VALUES
=
[]
# rubocop:disable Style/MutableConstant
attr_reader
:notes
attr_reader
:notes
...
@@ -11,12 +11,6 @@ class Discussion
...
@@ -11,12 +11,6 @@ class Discussion
:for_commit?
,
:for_commit?
,
:for_merge_request?
,
:for_merge_request?
,
:line_code
,
:original_line_code
,
:diff_file
,
:for_line?
,
:active?
,
to: :first_note
to: :first_note
delegate
:resolved_at
,
delegate
:resolved_at
,
...
@@ -25,29 +19,46 @@ class Discussion
...
@@ -25,29 +19,46 @@ class Discussion
to: :last_resolved_note
,
to: :last_resolved_note
,
allow_nil:
true
allow_nil:
true
de
legate
:blob
,
de
f
self
.
build
(
notes
,
noteable
=
nil
)
:highlighted_diff_lines
,
notes
.
first
.
discussion_class
(
noteable
).
new
(
notes
,
noteable
)
:diff_lines
,
end
to: :diff_file
,
def
self
.
build_collection
(
notes
,
noteable
=
nil
)
allow_nil:
true
notes
.
group_by
{
|
n
|
n
.
discussion_id
(
noteable
)
}.
values
.
map
{
|
notes
|
build
(
notes
,
noteable
)
}
end
def
self
.
for_notes
(
notes
)
def
self
.
discussion_id
(
note
)
notes
.
group_by
(
&
:discussion_id
).
values
.
map
{
|
notes
|
new
(
notes
)
}
Digest
::
SHA1
.
hexdigest
(
build_discussion_id
(
note
).
join
(
"-"
))
end
end
def
self
.
for_diff_notes
(
notes
)
# Optionally override the discussion ID at runtime depending on circumstances
notes
.
group_by
(
&
:line_code
).
values
.
map
{
|
notes
|
new
(
notes
)
}
def
self
.
override_discussion_id
(
note
)
nil
end
end
def
initialize
(
notes
)
def
self
.
build_discussion_id
(
note
)
@notes
=
notes
noteable_id
=
note
.
noteable_id
||
note
.
commit_id
[
:discussion
,
note
.
noteable_type
.
try
(
:underscore
),
noteable_id
]
end
end
def
last_resolved_note
def
self
.
original_discussion_id
(
note
)
return
unless
resolved?
original_discussion_id
=
build_original_discussion_id
(
note
)
if
original_discussion_id
Digest
::
SHA1
.
hexdigest
(
original_discussion_id
.
join
(
"-"
))
else
note
.
discussion_id
end
end
@last_resolved_note
||=
resolved_notes
.
sort_by
(
&
:resolved_at
).
last
# Optionally build a separate original discussion ID that will never change,
# if the main discussion ID _can_ change, like in the case of DiffDiscussion.
def
self
.
build_original_discussion_id
(
note
)
nil
end
def
initialize
(
notes
,
noteable
=
nil
)
@notes
=
notes
@noteable
=
noteable
end
end
def
last_updated_at
def
last_updated_at
...
@@ -59,42 +70,64 @@ class Discussion
...
@@ -59,42 +70,64 @@ class Discussion
end
end
def
id
def
id
first_note
.
discussion_id
first_note
.
discussion_id
(
noteable
)
end
end
alias_method
:to_param
,
:id
alias_method
:to_param
,
:id
def
original_id
first_note
.
original_discussion_id
end
def
diff_discussion?
def
diff_discussion?
first_note
.
diff_note?
false
end
def
render_as_individual_notes?
false
end
end
def
legacy_diff_discussion
?
def
potentially_resolvable
?
notes
.
any?
(
&
:legacy_diff_note?
)
first_note
.
for_merge_request?
end
end
def
resolvable?
def
resolvable?
return
@resolvable
if
@resolvable
.
present?
return
@resolvable
if
@resolvable
.
present?
@resolvable
=
diff_discussion
?
&&
notes
.
any?
(
&
:resolvable?
)
@resolvable
=
potentially_resolvable
?
&&
notes
.
any?
(
&
:resolvable?
)
end
end
MEMOIZED_VALUES
<<
:resolvable
def
resolved?
def
resolved?
return
@resolved
if
@resolved
.
present?
return
@resolved
if
@resolved
.
present?
@resolved
=
resolvable?
&&
notes
.
none?
(
&
:to_be_resolved?
)
@resolved
=
resolvable?
&&
notes
.
none?
(
&
:to_be_resolved?
)
end
end
MEMOIZED_VALUES
<<
:resolved
def
first_note
def
first_note
@first_note
||=
@
notes
.
first
@first_note
||=
notes
.
first
end
end
MEMOIZED_VALUES
<<
:first_note
def
first_note_to_resolve
def
first_note_to_resolve
@first_note_to_resolve
||=
notes
.
detect
(
&
:to_be_resolved?
)
return
unless
resolvable?
@first_note_to_resolve
||=
notes
.
find
(
&
:to_be_resolved?
)
end
end
MEMOIZED_VALUES
<<
:first_note_to_resolve
def
last_resolved_note
return
unless
resolved?
@last_resolved_note
||=
resolved_notes
.
sort_by
(
&
:resolved_at
).
last
end
MEMOIZED_VALUES
<<
:last_resolved_note
def
last_note
def
last_note
@last_note
||=
@
notes
.
last
@last_note
||=
notes
.
last
end
end
MEMOIZED_VALUES
<<
:last_note
def
resolved_notes
def
resolved_notes
notes
.
select
(
&
:resolved?
)
notes
.
select
(
&
:resolved?
)
...
@@ -124,25 +157,12 @@ class Discussion
...
@@ -124,25 +157,12 @@ class Discussion
update
{
|
notes
|
notes
.
unresolve!
}
update
{
|
notes
|
notes
.
unresolve!
}
end
end
def
for_target?
(
target
)
self
.
noteable
==
target
&&
!
diff_discussion?
end
def
active?
return
@active
if
@active
.
present?
@active
=
first_note
.
active?
end
def
collapsed?
def
collapsed?
return
false
unless
diff_discussion?
if
resolvable?
if
resolvable?
# New diff discussions only disappear once they are marked resolved
# New diff discussions only disappear once they are marked resolved
resolved?
resolved?
else
else
# Old diff discussions disappear once they become outdated
false
!
active?
end
end
end
end
...
@@ -151,52 +171,22 @@ class Discussion
...
@@ -151,52 +171,22 @@ class Discussion
end
end
def
reply_attributes
def
reply_attributes
data
=
{
first_note
.
slice
(
:type
,
:noteable_type
,
:noteable_id
,
:commit_id
)
noteable_type:
first_note
.
noteable_type
,
noteable_id:
first_note
.
noteable_id
,
commit_id:
first_note
.
commit_id
,
discussion_id:
self
.
id
,
}
if
diff_discussion?
data
[
:note_type
]
=
first_note
.
type
data
.
merge!
(
first_note
.
diff_attributes
)
end
data
end
# Returns an array of at most 16 highlighted lines above a diff note
def
truncated_diff_lines
(
highlight:
true
)
lines
=
highlight
?
highlighted_diff_lines
:
diff_lines
prev_lines
=
[]
lines
.
each
do
|
line
|
if
line
.
meta?
prev_lines
.
clear
else
prev_lines
<<
line
break
if
for_line?
(
line
)
prev_lines
.
shift
if
prev_lines
.
length
>=
NUMBER_OF_TRUNCATED_DIFF_LINES
end
end
prev_lines
end
end
private
private
def
update
def
update
notes_relation
=
DiffNote
.
where
(
id:
notes
.
map
(
&
:id
)).
fresh
# Do not select `Note.resolvable`, so that system notes remain in the collection
notes_relation
=
Note
.
where
(
id:
notes
.
map
(
&
:id
))
yield
(
notes_relation
)
yield
(
notes_relation
)
# Set the notes array to the updated notes
# Set the notes array to the updated notes
@notes
=
notes_relation
.
to_a
@notes
=
notes_relation
.
fresh
.
to_a
# Reset the memoized values
MEMOIZED_VALUES
.
each
do
|
var
|
@last_resolved_note
=
@resolvable
=
@resolved
=
@first_note
=
@last_note
=
nil
instance_variable_set
(
:"@
#{
var
}
"
,
nil
)
end
end
end
end
end
app/models/discussion_note.rb
0 → 100644
View file @
08bbb9fc
class
DiscussionNote
<
Note
NOTEABLE_TYPES
=
%w(MergeRequest)
.
freeze
validates
:noteable_type
,
inclusion:
{
in:
NOTEABLE_TYPES
}
def
discussion_class
(
*
)
SimpleDiscussion
end
end
app/models/individual_note_discussion.rb
0 → 100644
View file @
08bbb9fc
class
IndividualNoteDiscussion
<
Discussion
def
self
.
build_discussion_id
(
note
)
[
*
super
(
note
),
SecureRandom
.
hex
]
end
def
potentially_resolvable?
false
end
def
render_as_individual_notes?
true
end
end
app/models/issue.rb
View file @
08bbb9fc
...
@@ -3,6 +3,7 @@ require 'carrierwave/orm/activerecord'
...
@@ -3,6 +3,7 @@ require 'carrierwave/orm/activerecord'
class
Issue
<
ActiveRecord
::
Base
class
Issue
<
ActiveRecord
::
Base
include
InternalId
include
InternalId
include
Issuable
include
Issuable
include
Noteable
include
Referable
include
Referable
include
Sortable
include
Sortable
include
Spammable
include
Spammable
...
...
app/models/legacy_diff_discussion.rb
0 → 100644
View file @
08bbb9fc
class
LegacyDiffDiscussion
<
DiffDiscussion
def
self
.
unique_position_identifier
(
note
)
note
.
line_code
end
def
self
.
build_original_discussion_id
(
note
)
Discussion
.
build_original_discussion_id
(
note
)
end
def
legacy_diff_discussion?
true
end
def
potentially_resolvable?
false
end
def
collapsed?
!
active?
end
end
app/models/legacy_diff_note.rb
View file @
08bbb9fc
...
@@ -7,10 +7,8 @@ class LegacyDiffNote < Note
...
@@ -7,10 +7,8 @@ class LegacyDiffNote < Note
before_create
:set_diff
before_create
:set_diff
class
<<
self
def
discussion_class
(
*
)
def
build_discussion_id
(
noteable_type
,
noteable_id
,
line_code
)
LegacyDiffDiscussion
[
super
(
noteable_type
,
noteable_id
),
line_code
].
join
(
"-"
)
end
end
end
def
legacy_diff_note?
def
legacy_diff_note?
...
@@ -119,8 +117,4 @@ class LegacyDiffNote < Note
...
@@ -119,8 +117,4 @@ class LegacyDiffNote < Note
diffs
=
noteable
.
raw_diffs
(
Commit
.
max_diff_options
)
diffs
=
noteable
.
raw_diffs
(
Commit
.
max_diff_options
)
diffs
.
find
{
|
d
|
d
.
new_path
==
self
.
diff
.
new_path
}
diffs
.
find
{
|
d
|
d
.
new_path
==
self
.
diff
.
new_path
}
end
end
def
build_discussion_id
self
.
class
.
build_discussion_id
(
noteable_type
,
noteable_id
||
commit_id
,
line_code
)
end
end
end
app/models/merge_request.rb
View file @
08bbb9fc
class
MergeRequest
<
ActiveRecord
::
Base
class
MergeRequest
<
ActiveRecord
::
Base
include
InternalId
include
InternalId
include
Issuable
include
Issuable
include
Noteable
include
Referable
include
Referable
include
Sortable
include
Sortable
...
@@ -475,44 +476,32 @@ class MergeRequest < ActiveRecord::Base
...
@@ -475,44 +476,32 @@ class MergeRequest < ActiveRecord::Base
)
)
end
end
def
discussions
alias_method
:discussion_notes
,
:related_notes
@discussions
||=
self
.
related_notes
.
inc_relations_for_view
.
fresh
.
discussions
end
def
diff_discussions
@diff_discussions
||=
self
.
notes
.
diff_notes
.
discussions
end
def
resolvable_discussions
def
resolvable_discussions
@resolvable_discussions
||=
diff_discussions
.
select
(
&
:to_be_resolved?
)
@resolvable_discussions
||=
notes
.
resolvable
.
discussions
end
def
discussions_can_be_resolved_by?
(
user
)
resolvable_discussions
.
all?
{
|
discussion
|
discussion
.
can_resolve?
(
user
)
}
end
def
find_diff_discussion
(
discussion_id
)
notes
=
self
.
notes
.
diff_notes
.
where
(
discussion_id:
discussion_id
).
fresh
.
to_a
return
if
notes
.
empty?
Discussion
.
new
(
notes
)
end
end
def
discussions_resolvable?
def
discussions_resolvable?
diff
_discussions
.
any?
(
&
:resolvable?
)
resolvable
_discussions
.
any?
(
&
:resolvable?
)
end
end
def
discussions_resolved?
def
discussions_resolved?
discussions_resolvable?
&&
diff
_discussions
.
none?
(
&
:to_be_resolved?
)
discussions_resolvable?
&&
resolvable
_discussions
.
none?
(
&
:to_be_resolved?
)
end
end
def
discussions_to_be_resolved?
def
discussions_to_be_resolved?
discussions_resolvable?
&&
!
discussions_resolved?
discussions_resolvable?
&&
!
discussions_resolved?
end
end
def
discussions_to_be_resolved
@discussions_to_be_resolved
||=
resolvable_discussions
.
select
(
&
:to_be_resolved?
)
end
def
discussions_can_be_resolved_by?
(
user
)
discussions_to_be_resolved
.
all?
{
|
discussion
|
discussion
.
can_resolve?
(
user
)
}
end
def
mergeable_discussions_state?
def
mergeable_discussions_state?
return
true
unless
project
.
only_allow_merge_if_all_discussions_are_resolved?
return
true
unless
project
.
only_allow_merge_if_all_discussions_are_resolved?
...
...
app/models/note.rb
View file @
08bbb9fc
...
@@ -8,6 +8,7 @@ class Note < ActiveRecord::Base
...
@@ -8,6 +8,7 @@ class Note < ActiveRecord::Base
include
FasterCacheKeys
include
FasterCacheKeys
include
CacheMarkdownField
include
CacheMarkdownField
include
AfterCommitQueue
include
AfterCommitQueue
include
ResolvableNote
cache_markdown_field
:note
,
pipeline: :note
cache_markdown_field
:note
,
pipeline: :note
...
@@ -32,9 +33,6 @@ class Note < ActiveRecord::Base
...
@@ -32,9 +33,6 @@ class Note < ActiveRecord::Base
belongs_to
:author
,
class_name:
"User"
belongs_to
:author
,
class_name:
"User"
belongs_to
:updated_by
,
class_name:
"User"
belongs_to
:updated_by
,
class_name:
"User"
# Only used by DiffNote, but defined here so that it can be used in `Note.includes`
belongs_to
:resolved_by
,
class_name:
"User"
has_many
:todos
,
dependent: :destroy
has_many
:todos
,
dependent: :destroy
has_many
:events
,
as: :target
,
dependent: :destroy
has_many
:events
,
as: :target
,
dependent: :destroy
has_one
:system_note_metadata
has_one
:system_note_metadata
...
@@ -54,6 +52,7 @@ class Note < ActiveRecord::Base
...
@@ -54,6 +52,7 @@ class Note < ActiveRecord::Base
validates
:noteable_id
,
presence:
true
,
unless:
[
:for_commit?
,
:importing?
]
validates
:noteable_id
,
presence:
true
,
unless:
[
:for_commit?
,
:importing?
]
validates
:commit_id
,
presence:
true
,
if: :for_commit?
validates
:commit_id
,
presence:
true
,
if: :for_commit?
validates
:author
,
presence:
true
validates
:author
,
presence:
true
validates
:discussion_id
,
:original_discussion_id
,
presence:
true
,
format:
{
with:
/\A\h{40}\z/
}
validate
unless:
[
:for_commit?
,
:importing?
,
:for_personal_snippet?
]
do
|
note
|
validate
unless:
[
:for_commit?
,
:importing?
,
:for_personal_snippet?
]
do
|
note
|
unless
note
.
noteable
.
try
(
:project
)
==
note
.
project
unless
note
.
noteable
.
try
(
:project
)
==
note
.
project
...
@@ -76,7 +75,7 @@ class Note < ActiveRecord::Base
...
@@ -76,7 +75,7 @@ class Note < ActiveRecord::Base
end
end
scope
:diff_notes
,
->
{
where
(
type:
%w(LegacyDiffNote DiffNote)
)
}
scope
:diff_notes
,
->
{
where
(
type:
%w(LegacyDiffNote DiffNote)
)
}
scope
:non_diff_notes
,
->
{
where
(
type:
[
'Note'
,
nil
])
}
scope
:non_diff_notes
,
->
{
where
(
type:
[
'Note'
,
'DiscussionNote'
,
nil
])
}
scope
:with_associations
,
->
do
scope
:with_associations
,
->
do
# FYI noteable cannot be loaded for LegacyDiffNote for commits
# FYI noteable cannot be loaded for LegacyDiffNote for commits
...
@@ -84,9 +83,9 @@ class Note < ActiveRecord::Base
...
@@ -84,9 +83,9 @@ class Note < ActiveRecord::Base
project:
[
:project_members
,
{
group:
[
:group_members
]
}])
project:
[
:project_members
,
{
group:
[
:group_members
]
}])
end
end
after_initialize
:ensure_discussion_id
after_initialize
:ensure_discussion_id
,
:ensure_original_discussion_id
before_validation
:nullify_blank_type
,
:nullify_blank_line_code
before_validation
:nullify_blank_type
,
:nullify_blank_line_code
before_validation
:set_discussion_id
before_validation
:set_discussion_id
,
:set_original_discussion_id
,
on: :create
after_save
:keep_around_commit
,
unless: :for_personal_snippet?
after_save
:keep_around_commit
,
unless: :for_personal_snippet?
after_save
:expire_etag_cache
after_save
:expire_etag_cache
...
@@ -95,22 +94,31 @@ class Note < ActiveRecord::Base
...
@@ -95,22 +94,31 @@ class Note < ActiveRecord::Base
ActiveModel
::
Name
.
new
(
self
,
nil
,
'note'
)
ActiveModel
::
Name
.
new
(
self
,
nil
,
'note'
)
end
end
def
build_discussion_id
(
noteable_type
,
noteable_id
)
def
discussions
(
noteable
=
nil
)
[
:discussion
,
noteable_type
.
try
(
:underscore
),
noteable_id
].
join
(
"-"
)
Discussion
.
build_collection
(
fresh
,
noteable
)
end
end
def
discussion_id
(
*
args
)
def
find_original_discussion
(
discussion_id
)
Digest
::
SHA1
.
hexdigest
(
build_discussion_id
(
*
args
))
note
=
find_by
(
original_discussion_id:
discussion_id
)
return
unless
note
note
.
to_discussion
end
end
def
discussions
def
find_discussion
(
discussion_id
)
Discussion
.
for_notes
(
fresh
)
notes
=
where
(
discussion_id:
discussion_id
).
fresh
.
to_a
return
if
notes
.
empty?
Discussion
.
build
(
notes
)
end
end
def
grouped_diff_discussions
def
grouped_diff_discussions
active_notes
=
diff_notes
.
fresh
.
select
(
&
:active?
)
diff_notes
.
Discussion
.
for_diff_notes
(
active_notes
).
fresh
.
map
{
|
d
|
[
d
.
line_code
,
d
]
}.
to_h
select
(
&
:active?
).
group_by
(
&
:line_code
).
map
{
|
line_code
,
notes
|
[
line_code
,
DiffDiscussion
.
build
(
notes
)]
}.
to_h
end
end
def
count_for_collection
(
ids
,
type
)
def
count_for_collection
(
ids
,
type
)
...
@@ -121,7 +129,7 @@ class Note < ActiveRecord::Base
...
@@ -121,7 +129,7 @@ class Note < ActiveRecord::Base
end
end
def
cross_reference?
def
cross_reference?
system
&&
SystemNoteService
.
cross_reference?
(
note
)
system
?
&&
SystemNoteService
.
cross_reference?
(
note
)
end
end
def
diff_note?
def
diff_note?
...
@@ -140,18 +148,6 @@ class Note < ActiveRecord::Base
...
@@ -140,18 +148,6 @@ class Note < ActiveRecord::Base
true
true
end
end
def
resolvable?
false
end
def
resolved?
false
end
def
to_be_resolved?
resolvable?
&&
!
resolved?
end
def
max_attachment_size
def
max_attachment_size
current_application_settings
.
max_attachment_size
.
megabytes
.
to_i
current_application_settings
.
max_attachment_size
.
megabytes
.
to_i
end
end
...
@@ -228,7 +224,7 @@ class Note < ActiveRecord::Base
...
@@ -228,7 +224,7 @@ class Note < ActiveRecord::Base
end
end
def
can_be_award_emoji?
def
can_be_award_emoji?
noteable
.
is_a?
(
Awardable
)
noteable
.
is_a?
(
Awardable
)
&&
!
part_of_discussion?
end
end
def
contains_emoji_only?
def
contains_emoji_only?
...
@@ -239,6 +235,42 @@ class Note < ActiveRecord::Base
...
@@ -239,6 +235,42 @@ class Note < ActiveRecord::Base
for_personal_snippet?
?
'personal_snippet'
:
noteable_type
.
underscore
for_personal_snippet?
?
'personal_snippet'
:
noteable_type
.
underscore
end
end
def
can_be_discussion_note?
DiscussionNote
::
NOTEABLE_TYPES
.
include?
(
self
.
noteable_type
)
end
def
discussion_class
(
noteable
=
nil
)
# When commit notes are rendered on an MR's Discussion page, they are
# displayed in one discussion instead of individually
if
noteable
&&
noteable
!=
self
.
noteable
&&
for_commit?
CommitDiscussion
else
IndividualNoteDiscussion
end
end
def
discussion_id
(
noteable
=
nil
)
discussion_class
(
noteable
).
override_discussion_id
(
self
)
||
super
()
end
# Returns a discussion containing just this note
def
to_discussion
(
noteable
=
nil
)
Discussion
.
build
([
self
],
noteable
)
end
# Returns the entire discussion this note is part of
def
discussion
if
part_of_discussion?
self
.
noteable
.
notes
.
find_discussion
(
self
.
discussion_id
)
else
to_discussion
end
end
def
part_of_discussion?
!
to_discussion
.
render_as_individual_notes?
end
private
private
def
keep_around_commit
def
keep_around_commit
...
@@ -264,17 +296,21 @@ class Note < ActiveRecord::Base
...
@@ -264,17 +296,21 @@ class Note < ActiveRecord::Base
end
end
def
set_discussion_id
def
set_discussion_id
self
.
discussion_id
=
Digest
::
SHA1
.
hexdigest
(
build_discussion_id
)
self
.
discussion_id
||=
discussion_class
.
discussion_id
(
self
)
end
end
def
build_discussion_id
def
ensure_original_discussion_id
if
for_merge_request?
return
unless
self
.
persisted?
# Notes on merge requests are always in a discussion of their own,
# Needed in case the SELECT statement doesn't ask for `original_discussion_id`
# so we generate a unique discussion ID.
return
unless
self
.
has_attribute?
(:
original_discussion_id
)
[
:discussion
,
:note
,
SecureRandom
.
hex
].
join
(
"-"
)
return
if
self
.
original_discussion_id
else
self
.
class
.
build_discussion_id
(
noteable_type
,
noteable_id
||
commit_id
)
set_original_discussion_id
end
update_column
(
:original_discussion_id
,
self
.
original_discussion_id
)
end
def
set_original_discussion_id
self
.
original_discussion_id
=
discussion_class
.
original_discussion_id
(
self
)
end
end
def
expire_etag_cache
def
expire_etag_cache
...
...
app/models/sent_notification.rb
View file @
08bbb9fc
...
@@ -5,10 +5,11 @@ class SentNotification < ActiveRecord::Base
...
@@ -5,10 +5,11 @@ class SentNotification < ActiveRecord::Base
belongs_to
:noteable
,
polymorphic:
true
belongs_to
:noteable
,
polymorphic:
true
belongs_to
:recipient
,
class_name:
"User"
belongs_to
:recipient
,
class_name:
"User"
validates
:project
,
:recipient
,
:reply_key
,
presence:
true
validates
:project
,
:recipient
,
presence:
true
validates
:reply_key
,
uniqueness:
true
validates
:reply_key
,
presence:
true
,
uniqueness:
true
validates
:noteable_id
,
presence:
true
,
unless: :for_commit?
validates
:noteable_id
,
presence:
true
,
unless: :for_commit?
validates
:commit_id
,
presence:
true
,
if: :for_commit?
validates
:commit_id
,
presence:
true
,
if: :for_commit?
validates
:in_reply_to_discussion_id
,
format:
{
with:
/\A\h{40}\z/
,
allow_nil:
true
}
validate
:note_valid
validate
:note_valid
after_save
:keep_around_commit
after_save
:keep_around_commit
...
@@ -34,23 +35,20 @@ class SentNotification < ActiveRecord::Base
...
@@ -34,23 +35,20 @@ class SentNotification < ActiveRecord::Base
end
end
attrs
.
reverse_merge!
(
attrs
.
reverse_merge!
(
project:
noteable
.
project
,
project:
noteable
.
project
,
noteable_type:
noteable
.
class
.
name
,
recipient_id:
recipient_id
,
noteable_id:
noteable_id
,
reply_key:
reply_key
,
commit_id:
commit_id
,
recipient_id:
recipient_id
,
noteable_type:
noteable
.
class
.
name
,
reply_key:
reply_key
noteable_id:
noteable_id
,
commit_id:
commit_id
,
)
)
create
(
attrs
)
create
(
attrs
)
end
end
def
record_note
(
note
,
recipient_id
,
reply_key
,
attrs
=
{})
def
record_note
(
note
,
recipient_id
,
reply_key
,
attrs
=
{})
if
note
.
diff_note?
attrs
[
:in_reply_to_discussion_id
]
=
note
.
original_discussion_id
attrs
[
:note_type
]
=
note
.
type
attrs
.
merge!
(
note
.
diff_attributes
)
end
record
(
note
.
noteable
,
recipient_id
,
reply_key
,
attrs
)
record
(
note
.
noteable
,
recipient_id
,
reply_key
,
attrs
)
end
end
...
@@ -89,31 +87,34 @@ class SentNotification < ActiveRecord::Base
...
@@ -89,31 +87,34 @@ class SentNotification < ActiveRecord::Base
self
.
reply_key
self
.
reply_key
end
end
def
note_attributes
def
note_params
{
attrs
=
{
project:
self
.
project
,
noteable_type:
self
.
noteable_type
,
author:
self
.
recipient
,
noteable_id:
self
.
noteable_id
,
type:
self
.
note_type
,
commit_id:
self
.
commit_id
noteable_type:
self
.
noteable_type
,
noteable_id:
self
.
noteable_id
,
commit_id:
self
.
commit_id
,
line_code:
self
.
line_code
,
position:
self
.
position
.
to_json
}
}
end
def
create_note
(
note
)
if
self
.
in_reply_to_discussion_id
.
present?
Notes
::
CreateService
.
new
(
attrs
[
:in_reply_to_discussion_id
]
=
self
.
in_reply_to_discussion_id
self
.
project
,
else
self
.
recipient
,
attrs
.
merge!
(
self
.
note_attributes
.
merge
(
note:
note
)
type:
self
.
note_type
,
).
execute
# LegacyDiffNote
line_code:
self
.
line_code
,
# DiffNote
position:
self
.
position
.
to_json
)
end
attrs
end
end
private
private
def
note_valid
def
note_valid
Note
.
new
(
note_attributes
.
merge
(
note:
"Test"
))
.
valid?
Note
s
::
BuildService
.
new
(
self
.
project
,
self
.
recipient
,
note_params
.
merge
(
note:
'Test'
)).
execute
.
valid?
end
end
def
keep_around_commit
def
keep_around_commit
...
...
app/models/simple_discussion.rb
0 → 100644
View file @
08bbb9fc
class
SimpleDiscussion
<
Discussion
def
self
.
build_discussion_id
(
note
)
[
*
super
(
note
),
SecureRandom
.
hex
]
end
def
reply_attributes
super
.
merge
(
discussion_id:
self
.
id
)
end
end
app/models/snippet.rb
View file @
08bbb9fc
...
@@ -2,6 +2,7 @@ class Snippet < ActiveRecord::Base
...
@@ -2,6 +2,7 @@ class Snippet < ActiveRecord::Base
include
Gitlab
::
VisibilityLevel
include
Gitlab
::
VisibilityLevel
include
Linguist
::
BlobHelper
include
Linguist
::
BlobHelper
include
CacheMarkdownField
include
CacheMarkdownField
include
Noteable
include
Participable
include
Participable
include
Referable
include
Referable
include
Sortable
include
Sortable
...
...
app/services/concerns/issues/resolve_discussions.rb
View file @
08bbb9fc
...
@@ -25,7 +25,7 @@ module Issues
...
@@ -25,7 +25,7 @@ module Issues
Array
(
discussion_or_nil
)
Array
(
discussion_or_nil
)
else
else
merge_request_to_resolve_discussions_of
merge_request_to_resolve_discussions_of
.
resolvable_discussions
.
discussions_to_be_resolved
end
end
end
end
end
end
...
...
app/services/notes/build_service.rb
0 → 100644
View file @
08bbb9fc
module
Notes
class
BuildService
<
BaseService
def
execute
# TODO: Remove when we use a selectbox instead of a submit button
params
[
:type
]
=
DiscussionNote
.
name
if
params
.
delete
(
:new_discussion
)
in_reply_to_discussion_id
=
params
.
delete
(
:in_reply_to_discussion_id
)
if
project
&&
in_reply_to_discussion_id
.
present?
discussion
=
project
.
notes
.
find_original_discussion
(
in_reply_to_discussion_id
)
||
project
.
notes
.
find_discussion
(
in_reply_to_discussion_id
)
unless
discussion
note
=
Note
.
new
note
.
errors
.
add
(
:base
,
'Discussion to reply to cannot be found'
)
return
note
end
params
.
merge!
(
discussion
.
reply_attributes
)
end
note
=
Note
.
new
(
params
)
note
.
project
=
project
note
.
author
=
current_user
note
end
end
end
app/services/notes/create_service.rb
View file @
08bbb9fc
...
@@ -3,10 +3,8 @@ module Notes
...
@@ -3,10 +3,8 @@ module Notes
def
execute
def
execute
merge_request_diff_head_sha
=
params
.
delete
(
:merge_request_diff_head_sha
)
merge_request_diff_head_sha
=
params
.
delete
(
:merge_request_diff_head_sha
)
note
=
Note
.
new
(
params
)
note
=
Notes
::
BuildService
.
new
(
project
,
current_user
,
params
).
execute
note
.
project
=
project
return
note
unless
note
.
valid?
note
.
author
=
current_user
note
.
system
=
false
# We execute commands (extracted from `params[:note]`) on the noteable
# We execute commands (extracted from `params[:note]`) on the noteable
# **before** we save the note because if the note consists of commands
# **before** we save the note because if the note consists of commands
...
...
app/services/system_note_service.rb
View file @
08bbb9fc
...
@@ -228,12 +228,10 @@ module SystemNoteService
...
@@ -228,12 +228,10 @@ module SystemNoteService
def
discussion_continued_in_issue
(
discussion
,
project
,
author
,
issue
)
def
discussion_continued_in_issue
(
discussion
,
project
,
author
,
issue
)
body
=
"created
#{
issue
.
to_reference
}
to continue this discussion"
body
=
"created
#{
issue
.
to_reference
}
to continue this discussion"
note_attributes
=
discussion
.
reply_attributes
.
merge
(
project:
project
,
author:
author
,
note:
body
)
note_params
=
discussion
.
reply_attributes
.
merge
(
project:
project
,
author:
author
,
note:
body
)
note
=
Note
.
create
(
note_attributes
.
merge
(
system:
true
))
note_params
[
:type
]
=
note_params
.
delete
(
:note_type
)
note
.
system_note_metadata
=
SystemNoteMetadata
.
new
(
action:
'discussion'
)
note
=
Note
.
create
(
note_params
.
merge
(
system:
true
))
note
.
system_note_metadata
=
SystemNoteMetadata
.
new
({
action:
'discussion'
})
note
note
end
end
...
...
app/views/discussions/_discussion.html.haml
View file @
08bbb9fc
...
@@ -18,19 +18,21 @@
...
@@ -18,19 +18,21 @@
.inline.discussion-headline-light
.inline.discussion-headline-light
=
discussion
.
author
.
to_reference
=
discussion
.
author
.
to_reference
started a discussion
on
started a discussion
-
if
discussion
.
for_commit?
-
if
discussion
.
for_commit?
&&
@noteable
!=
discussion
.
noteable
on
-
commit
=
discussion
.
noteable
-
commit
=
discussion
.
noteable
-
if
commit
-
if
commit
commit
commit
=
link_to
commit
.
short_id
,
namespace_project_commit_path
(
discussion
.
project
.
namespace
,
discussion
.
project
,
discussion
.
noteable
,
anchor:
discussion
.
line_code
),
class:
'monospace'
-
anchor
=
discussion
.
line_code
if
discussion
.
diff_discussion?
=
link_to
commit
.
short_id
,
namespace_project_commit_path
(
discussion
.
project
.
namespace
,
discussion
.
project
,
discussion
.
noteable
,
anchor:
anchor
),
class:
'monospace'
-
else
-
else
a deleted commit
a deleted commit
-
else
-
elsif
discussion
.
diff_discussion?
on
-
if
discussion
.
active?
-
if
discussion
.
active?
=
link_to
diffs_namespace_project_merge_request_path
(
discussion
.
project
.
namespace
,
discussion
.
project
,
discussion
.
noteable
,
anchor:
discussion
.
line_code
)
do
=
link_to
'the diff'
,
discussion_diff_path
(
discussion
)
the diff
-
else
-
else
an outdated diff
an outdated diff
...
...
app/views/discussions/_notes.html.haml
View file @
08bbb9fc
%ul
.notes
{
data:
{
discussion_id:
discussion
.
id
}
}
%ul
.notes
{
data:
{
discussion_id:
discussion
.
id
,
original_discussion_id:
discussion
.
original_id
}
}
=
render
partial:
"projects/notes/note"
,
collection:
discussion
.
notes
,
as: :note
=
render
partial:
"projects/notes/note"
,
collection:
discussion
.
notes
,
as: :note
-
if
current_user
-
if
current_user
.discussion-reply-holder
.discussion-reply-holder
-
if
discussion
.
diff_discussion
?
-
if
discussion
.
potentially_resolvable
?
-
line_type
=
local_assigns
.
fetch
(
:line_type
,
nil
)
-
line_type
=
local_assigns
.
fetch
(
:line_type
,
nil
)
.btn-group-justified.discussion-with-resolve-btn
{
role:
"group"
}
.btn-group-justified.discussion-with-resolve-btn
{
role:
"group"
}
.btn-group
{
role:
"group"
}
.btn-group
{
role:
"group"
}
=
link_to_reply_discussion
(
discussion
,
line_type
)
=
link_to_reply_discussion
(
discussion
,
line_type
)
=
render
"discussions/resolve_all"
,
discussion:
discussion
=
render
"discussions/resolve_all"
,
discussion:
discussion
-
if
discussion
.
for_merge_request?
.btn-group.discussion-actions
.btn-group.discussion-actions
=
render
"discussions/new_issue_for_discussion"
,
discussion:
discussion
,
merge_request:
discussion
.
noteable
=
render
"discussions/new_issue_for_discussion"
,
discussion:
discussion
,
merge_request:
discussion
.
noteable
=
render
"discussions/jump_to_next"
,
discussion:
discussion
=
render
"discussions/jump_to_next"
,
discussion:
discussion
-
else
-
else
=
link_to_reply_discussion
(
discussion
)
=
link_to_reply_discussion
(
discussion
)
app/views/discussions/_resolve_all.html.haml
View file @
08bbb9fc
-
if
discussion
.
for_merge_request?
%resolve-discussion-btn
{
":discussion-id"
=>
"'#{discussion.id}'"
,
%resolve-discussion-btn
{
":discussion-id"
=>
"'#{discussion.id}'"
,
":merge-request-id"
=>
discussion
.
noteable
.
iid
,
":merge-request-id"
=>
discussion
.
noteable
.
iid
,
":can-resolve"
=>
discussion
.
can_resolve?
(
current_user
),
":can-resolve"
=>
discussion
.
can_resolve?
(
current_user
),
"inline-template"
=>
true
}
"inline-template"
=>
true
}
.btn-group
{
role:
"group"
,
"v-if"
=>
"showButton"
}
.btn-group
{
role:
"group"
,
"v-if"
=>
"showButton"
}
%button
.btn.btn-default
{
type:
"button"
,
"@click"
=>
"resolve"
,
":disabled"
=>
"loading"
,
"v-cloak"
=>
"true"
}
%button
.btn.btn-default
{
type:
"button"
,
"@click"
=>
"resolve"
,
":disabled"
=>
"loading"
,
"v-cloak"
=>
"true"
}
=
icon
(
"spinner spin"
,
"v-show"
=>
"loading"
)
=
icon
(
"spinner spin"
,
"v-show"
=>
"loading"
)
{{ buttonText }}
{{ buttonText }}
app/views/projects/commit/show.html.haml
View file @
08bbb9fc
...
@@ -10,6 +10,7 @@
...
@@ -10,6 +10,7 @@
-
else
-
else
.block-connector
.block-connector
=
render
"projects/diffs/diffs"
,
diffs:
@diffs
,
environment:
@environment
=
render
"projects/diffs/diffs"
,
diffs:
@diffs
,
environment:
@environment
=
render
"projects/notes/notes_with_form"
=
render
"projects/notes/notes_with_form"
-
if
can_collaborate_with_project?
-
if
can_collaborate_with_project?
-
%w(revert cherry-pick)
.
each
do
|
type
|
-
%w(revert cherry-pick)
.
each
do
|
type
|
...
...
app/views/projects/notes/_form.html.haml
View file @
08bbb9fc
...
@@ -4,12 +4,18 @@
...
@@ -4,12 +4,18 @@
=
hidden_field_tag
:view
,
diff_view
=
hidden_field_tag
:view
,
diff_view
=
hidden_field_tag
:line_type
=
hidden_field_tag
:line_type
=
hidden_field_tag
:merge_request_diff_head_sha
,
@note
.
noteable
.
try
(
:diff_head_sha
)
=
hidden_field_tag
:merge_request_diff_head_sha
,
@note
.
noteable
.
try
(
:diff_head_sha
)
=
hidden_field_tag
:in_reply_to_discussion_id
=
note_target_fields
(
@note
)
=
note_target_fields
(
@note
)
=
f
.
hidden_field
:commit_id
=
f
.
hidden_field
:line_code
=
f
.
hidden_field
:noteable_id
=
f
.
hidden_field
:noteable_type
=
f
.
hidden_field
:noteable_type
=
f
.
hidden_field
:noteable_id
=
f
.
hidden_field
:commit_id
=
f
.
hidden_field
:type
=
f
.
hidden_field
:type
-# LegacyDiffNote
=
f
.
hidden_field
:line_code
-# DiffNote
=
f
.
hidden_field
:position
=
f
.
hidden_field
:position
=
render
layout:
'projects/md_preview'
,
locals:
{
preview_class:
"md-preview"
,
referenced_users:
true
}
do
=
render
layout:
'projects/md_preview'
,
locals:
{
preview_class:
"md-preview"
,
referenced_users:
true
}
do
...
@@ -23,6 +29,11 @@
...
@@ -23,6 +29,11 @@
.note-form-actions.clearfix
.note-form-actions.clearfix
=
f
.
submit
'Comment'
,
class:
"btn btn-nr btn-create append-right-10 comment-btn js-comment-button"
=
f
.
submit
'Comment'
,
class:
"btn btn-nr btn-create append-right-10 comment-btn js-comment-button"
-
if
@note
.
can_be_discussion_note?
=
submit_tag
'Start discussion'
,
name:
'new_discussion'
,
class:
"btn btn-nr append-right-10 btn-inverted js-note-new-discussion"
=
yield
(
:note_actions
)
=
yield
(
:note_actions
)
%a
.btn.btn-cancel.js-note-discard
{
role:
"button"
,
data:
{
cancel_text:
"Cancel"
}
}
%a
.btn.btn-cancel.js-note-discard
{
role:
"button"
,
data:
{
cancel_text:
"Cancel"
}
}
Discard draft
Discard draft
app/views/projects/notes/_note.html.haml
View file @
08bbb9fc
...
@@ -31,7 +31,7 @@
...
@@ -31,7 +31,7 @@
-
if
note
.
resolvable?
-
if
note
.
resolvable?
-
can_resolve
=
can?
(
current_user
,
:resolve_note
,
note
)
-
can_resolve
=
can?
(
current_user
,
:resolve_note
,
note
)
%resolve-btn
{
"project-path"
=>
project_path
(
note
.
project
),
%resolve-btn
{
"project-path"
=>
project_path
(
note
.
project
),
"discussion-id"
=>
note
.
discussion_id
,
"discussion-id"
=>
note
.
discussion_id
(
@noteable
)
,
":note-id"
=>
note
.
id
,
":note-id"
=>
note
.
id
,
":resolved"
=>
note
.
resolved?
,
":resolved"
=>
note
.
resolved?
,
":can-resolve"
=>
can_resolve
,
":can-resolve"
=>
can_resolve
,
...
...
app/views/projects/notes/_notes.html.haml
View file @
08bbb9fc
-
if
@discussions
.
present?
-
if
@discussions
.
present?
-
@discussions
.
each
do
|
discussion
|
-
@discussions
.
each
do
|
discussion
|
-
if
discussion
.
for_target?
(
@noteable
)
-
if
discussion
.
render_as_individual_notes?
=
render
partial:
"projects/notes/note"
,
object:
discussion
.
first_note
,
as: :note
=
render
partial:
"projects/notes/note"
,
collection:
discussion
.
notes
,
as: :note
-
else
-
else
=
render
'discussions/discussion'
,
discussion:
discussion
=
render
'discussions/discussion'
,
discussion:
discussion
-
else
-
else
...
...
changelogs/unreleased/new-resolvable-discussion.yml
0 → 100644
View file @
08bbb9fc
---
title
:
Add option to start a new resolvable discussion in an MR
merge_request
:
author
:
db/migrate/20161128095517_add_in_reply_to_discussion_id_to_sent_notifications.rb
0 → 100644
View file @
08bbb9fc
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class
AddInReplyToDiscussionIdToSentNotifications
<
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
change
add_column
:sent_notifications
,
:in_reply_to_discussion_id
,
:string
end
end
db/migrate/20170308220217_add_index_to_note_original_discussion_id.rb
0 → 100644
View file @
08bbb9fc
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class
AddIndexToNoteOriginalDiscussionId
<
ActiveRecord
::
Migration
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
disable_ddl_transaction!
def
up
add_concurrent_index
:notes
,
:original_discussion_id
end
def
down
remove_index
:notes
,
:original_discussion_id
if
index_exists?
:notes
,
:original_discussion_id
end
end
db/schema.rb
View file @
08bbb9fc
...
@@ -736,6 +736,7 @@ ActiveRecord::Schema.define(version: 20170402231018) do
...
@@ -736,6 +736,7 @@ ActiveRecord::Schema.define(version: 20170402231018) do
add_index
"notes"
,
[
"note"
],
name:
"index_notes_on_note_trigram"
,
using: :gin
,
opclasses:
{
"note"
=>
"gin_trgm_ops"
}
add_index
"notes"
,
[
"note"
],
name:
"index_notes_on_note_trigram"
,
using: :gin
,
opclasses:
{
"note"
=>
"gin_trgm_ops"
}
add_index
"notes"
,
[
"noteable_id"
,
"noteable_type"
],
name:
"index_notes_on_noteable_id_and_noteable_type"
,
using: :btree
add_index
"notes"
,
[
"noteable_id"
,
"noteable_type"
],
name:
"index_notes_on_noteable_id_and_noteable_type"
,
using: :btree
add_index
"notes"
,
[
"noteable_type"
],
name:
"index_notes_on_noteable_type"
,
using: :btree
add_index
"notes"
,
[
"noteable_type"
],
name:
"index_notes_on_noteable_type"
,
using: :btree
add_index
"notes"
,
[
"original_discussion_id"
],
name:
"index_notes_on_original_discussion_id"
,
using: :btree
add_index
"notes"
,
[
"project_id"
,
"noteable_type"
],
name:
"index_notes_on_project_id_and_noteable_type"
,
using: :btree
add_index
"notes"
,
[
"project_id"
,
"noteable_type"
],
name:
"index_notes_on_project_id_and_noteable_type"
,
using: :btree
add_index
"notes"
,
[
"updated_at"
],
name:
"index_notes_on_updated_at"
,
using: :btree
add_index
"notes"
,
[
"updated_at"
],
name:
"index_notes_on_updated_at"
,
using: :btree
...
@@ -999,6 +1000,7 @@ ActiveRecord::Schema.define(version: 20170402231018) do
...
@@ -999,6 +1000,7 @@ ActiveRecord::Schema.define(version: 20170402231018) do
t
.
string
"line_code"
t
.
string
"line_code"
t
.
string
"note_type"
t
.
string
"note_type"
t
.
text
"position"
t
.
text
"position"
t
.
string
"in_reply_to_discussion_id"
end
end
add_index
"sent_notifications"
,
[
"reply_key"
],
name:
"index_sent_notifications_on_reply_key"
,
unique:
true
,
using: :btree
add_index
"sent_notifications"
,
[
"reply_key"
],
name:
"index_sent_notifications_on_reply_key"
,
unique:
true
,
using: :btree
...
...
lib/gitlab/email/handler/create_note_handler.rb
View file @
08bbb9fc
...
@@ -45,13 +45,7 @@ module Gitlab
...
@@ -45,13 +45,7 @@ module Gitlab
Notes
::
CreateService
.
new
(
Notes
::
CreateService
.
new
(
project
,
project
,
author
,
author
,
note:
message
,
sent_notification
.
note_params
.
merge
(
note:
message
)
noteable_type:
sent_notification
.
noteable_type
,
noteable_id:
sent_notification
.
noteable_id
,
commit_id:
sent_notification
.
commit_id
,
line_code:
sent_notification
.
line_code
,
position:
sent_notification
.
position
,
type:
sent_notification
.
note_type
).
execute
).
execute
end
end
end
end
...
...
spec/controllers/projects/commit_controller_spec.rb
View file @
08bbb9fc
...
@@ -266,7 +266,7 @@ describe Projects::CommitController do
...
@@ -266,7 +266,7 @@ describe Projects::CommitController do
diff_for_path
(
id:
commit2
.
id
,
old_path:
existing_path
,
new_path:
existing_path
)
diff_for_path
(
id:
commit2
.
id
,
old_path:
existing_path
,
new_path:
existing_path
)
expect
(
assigns
(
:diff_notes_disabled
)).
to
be_falsey
expect
(
assigns
(
:diff_notes_disabled
)).
to
be_falsey
expect
(
assigns
(
:
comments_target
)).
to
eq
(
noteable_type:
'Commit'
,
expect
(
assigns
(
:
new_diff_note_attrs
)).
to
eq
(
noteable_type:
'Commit'
,
commit_id:
commit2
.
id
)
commit_id:
commit2
.
id
)
end
end
...
...
spec/controllers/projects/discussions_controller_spec.rb
View file @
08bbb9fc
...
@@ -4,7 +4,7 @@ describe Projects::DiscussionsController do
...
@@ -4,7 +4,7 @@ describe Projects::DiscussionsController do
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:merge_request
)
{
create
(
:merge_request
)
}
let
(
:merge_request
)
{
create
(
:merge_request
)
}
let
(
:project
)
{
merge_request
.
source_project
}
let
(
:project
)
{
merge_request
.
source_project
}
let
(
:note
)
{
create
(
:di
ff
_note_on_merge_request
,
noteable:
merge_request
,
project:
project
)
}
let
(
:note
)
{
create
(
:di
scussion
_note_on_merge_request
,
noteable:
merge_request
,
project:
project
)
}
let
(
:discussion
)
{
note
.
discussion
}
let
(
:discussion
)
{
note
.
discussion
}
let
(
:request_params
)
do
let
(
:request_params
)
do
...
...
spec/controllers/projects/issues_controller_spec.rb
View file @
08bbb9fc
...
@@ -508,7 +508,7 @@ describe Projects::IssuesController do
...
@@ -508,7 +508,7 @@ describe Projects::IssuesController do
end
end
context
'resolving discussions in MergeRequest'
do
context
'resolving discussions in MergeRequest'
do
let
(
:discussion
)
{
Discussion
.
for_diff_notes
([
create
(
:diff_note_on_merge_request
)]).
first
}
let
(
:discussion
)
{
create
(
:diff_note_on_merge_request
).
to_discussion
}
let
(
:merge_request
)
{
discussion
.
noteable
}
let
(
:merge_request
)
{
discussion
.
noteable
}
let
(
:project
)
{
merge_request
.
source_project
}
let
(
:project
)
{
merge_request
.
source_project
}
...
...
spec/controllers/projects/merge_requests_controller_spec.rb
View file @
08bbb9fc
...
@@ -574,7 +574,7 @@ describe Projects::MergeRequestsController do
...
@@ -574,7 +574,7 @@ describe Projects::MergeRequestsController do
diff_for_path
(
id:
merge_request
.
iid
,
old_path:
existing_path
,
new_path:
existing_path
)
diff_for_path
(
id:
merge_request
.
iid
,
old_path:
existing_path
,
new_path:
existing_path
)
expect
(
assigns
(
:diff_notes_disabled
)).
to
be_falsey
expect
(
assigns
(
:diff_notes_disabled
)).
to
be_falsey
expect
(
assigns
(
:
comments_target
)).
to
eq
(
noteable_type:
'MergeRequest'
,
expect
(
assigns
(
:
new_diff_note_attrs
)).
to
eq
(
noteable_type:
'MergeRequest'
,
noteable_id:
merge_request
.
id
)
noteable_id:
merge_request
.
id
)
end
end
...
...
spec/controllers/projects/notes_controller_spec.rb
View file @
08bbb9fc
...
@@ -14,7 +14,15 @@ describe Projects::NotesController do
...
@@ -14,7 +14,15 @@ describe Projects::NotesController do
}
}
end
end
describe
'GET index'
do
# It renders the discussion partial for any threaded note
# TODO: Test
end
describe
'POST create'
do
describe
'POST create'
do
# Test :type, :new_discussion, :in_reply_to_discussion_id (in_reply_to_id?)
# TODO: Test
let
(
:merge_request
)
{
create
(
:merge_request
)
}
let
(
:merge_request
)
{
create
(
:merge_request
)
}
let
(
:project
)
{
merge_request
.
source_project
}
let
(
:project
)
{
merge_request
.
source_project
}
let
(
:request_params
)
do
let
(
:request_params
)
do
...
@@ -49,7 +57,8 @@ describe Projects::NotesController do
...
@@ -49,7 +57,8 @@ describe Projects::NotesController do
note:
'some note'
,
note:
'some note'
,
noteable_id:
merge_request
.
id
.
to_s
,
noteable_id:
merge_request
.
id
.
to_s
,
noteable_type:
'MergeRequest'
,
noteable_type:
'MergeRequest'
,
merge_request_diff_head_sha:
'sha'
merge_request_diff_head_sha:
'sha'
,
in_reply_to_discussion_id:
nil
}
}
expect
(
Notes
::
CreateService
).
to
receive
(
:new
).
with
(
project
,
user
,
service_params
).
and_return
(
double
(
execute:
true
))
expect
(
Notes
::
CreateService
).
to
receive
(
:new
).
with
(
project
,
user
,
service_params
).
and_return
(
double
(
execute:
true
))
...
...
spec/factories/discussions.rb
0 → 100644
View file @
08bbb9fc
FactoryGirl
.
define
do
factory
:discussion
do
# TODO: Implement
end
end
spec/factories/notes.rb
View file @
08bbb9fc
...
@@ -16,6 +16,15 @@ FactoryGirl.define do
...
@@ -16,6 +16,15 @@ FactoryGirl.define do
factory
:note_on_personal_snippet
,
traits:
[
:on_personal_snippet
]
factory
:note_on_personal_snippet
,
traits:
[
:on_personal_snippet
]
factory
:system_note
,
traits:
[
:system
]
factory
:system_note
,
traits:
[
:system
]
factory
:discussion_note_on_merge_request
,
traits:
[
:on_merge_request
],
class:
DiscussionNote
do
association
:project
,
:repository
trait
:resolved
do
resolved_at
{
Time
.
now
}
resolved_by
{
create
(
:user
)
}
end
end
factory
:legacy_diff_note_on_commit
,
traits:
[
:on_commit
,
:legacy_diff_note
],
class:
LegacyDiffNote
do
factory
:legacy_diff_note_on_commit
,
traits:
[
:on_commit
,
:legacy_diff_note
],
class:
LegacyDiffNote
do
association
:project
,
:repository
association
:project
,
:repository
end
end
...
...
spec/features/issues/create_issue_for_discussions_in_merge_request_spec.rb
View file @
08bbb9fc
...
@@ -4,7 +4,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
...
@@ -4,7 +4,7 @@ feature 'Resolving all open discussions in a merge request from an issue', featu
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:project
)
{
create
(
:project
)
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
)
}
let
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
)
}
let!
(
:discussion
)
{
Discussion
.
for_diff_notes
([
create
(
:diff_note_on_merge_request
,
noteable:
merge_request
,
project:
project
)]).
first
}
let!
(
:discussion
)
{
create
(
:diff_note_on_merge_request
,
noteable:
merge_request
,
project:
project
).
to_discussion
}
describe
'as a user with access to the project'
do
describe
'as a user with access to the project'
do
before
do
before
do
...
...
spec/features/notes_on_merge_requests_spec.rb
View file @
08bbb9fc
...
@@ -25,7 +25,7 @@ describe 'Comments', feature: true do
...
@@ -25,7 +25,7 @@ describe 'Comments', feature: true do
describe
'the note form'
do
describe
'the note form'
do
it
'is valid'
do
it
'is valid'
do
is_expected
.
to
have_css
(
'.js-main-target-form'
,
visible:
true
,
count:
1
)
is_expected
.
to
have_css
(
'.js-main-target-form'
,
visible:
true
,
count:
1
)
expect
(
find
(
'.js-main-target-form
input[type=submit]
'
).
value
).
expect
(
find
(
'.js-main-target-form
.js-comment-button
'
).
value
).
to
eq
(
'Comment'
)
to
eq
(
'Comment'
)
page
.
within
(
'.js-main-target-form'
)
do
page
.
within
(
'.js-main-target-form'
)
do
expect
(
page
).
not_to
have_link
(
'Cancel'
)
expect
(
page
).
not_to
have_link
(
'Cancel'
)
...
...
spec/finders/notes_finder_spec.rb
View file @
08bbb9fc
...
@@ -202,4 +202,8 @@ describe NotesFinder do
...
@@ -202,4 +202,8 @@ describe NotesFinder do
end
end
end
end
end
end
describe
'#target'
do
# TODO: Test
end
end
end
spec/models/commit_discussion_spec.rb
0 → 100644
View file @
08bbb9fc
require
'spec_helper'
describe
CommitDiscussion
,
model:
true
do
# TODO: Test
end
spec/models/concerns/noteable_spec.rb
0 → 100644
View file @
08bbb9fc
require
'spec_helper'
describe
Noteable
,
model:
true
do
# TODO: Test
end
spec/models/concerns/resolvable_note_spec.rb
0 → 100644
View file @
08bbb9fc
require
'spec_helper'
describe
Note
,
ResolvableNote
,
models:
true
do
subject
{
create
(
:discussion_note_on_merge_request
)
}
describe
'.resolvable'
do
# TODO: Test
end
describe
'.resolved'
do
# TODO: Test
end
describe
'.unresolved'
do
# TODO: Test
end
describe
".resolve!"
do
let
(
:current_user
)
{
create
(
:user
)
}
let!
(
:commit_note
)
{
create
(
:diff_note_on_commit
)
}
let!
(
:resolved_note
)
{
create
(
:discussion_note_on_merge_request
,
:resolved
)
}
let!
(
:unresolved_note
)
{
create
(
:discussion_note_on_merge_request
)
}
before
do
described_class
.
resolve!
(
current_user
)
commit_note
.
reload
resolved_note
.
reload
unresolved_note
.
reload
end
it
'resolves only the resolvable, not yet resolved notes'
do
expect
(
commit_note
.
resolved_at
).
to
be_nil
expect
(
resolved_note
.
resolved_by
).
not_to
eq
(
current_user
)
expect
(
unresolved_note
.
resolved_at
).
not_to
be_nil
expect
(
unresolved_note
.
resolved_by
).
to
eq
(
current_user
)
end
end
describe
".unresolve!"
do
let!
(
:resolved_note
)
{
create
(
:discussion_note_on_merge_request
,
:resolved
)
}
before
do
described_class
.
unresolve!
resolved_note
.
reload
end
it
'unresolves the resolved notes'
do
expect
(
resolved_note
.
resolved_by
).
to
be_nil
expect
(
resolved_note
.
resolved_at
).
to
be_nil
end
end
describe
'#resolvable?'
do
context
"when potentially resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:discussion_resolvable?
).
and_return
(
true
)
end
context
"when a system note"
do
before
do
subject
.
system
=
true
end
it
"returns false"
do
expect
(
subject
.
resolvable?
).
to
be
false
end
end
context
"when a regular note"
do
it
"returns true"
do
expect
(
subject
.
resolvable?
).
to
be
true
end
end
end
context
"when not potentially resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:discussion_resolvable?
).
and_return
(
false
)
end
it
"returns false"
do
expect
(
subject
.
resolvable?
).
to
be
false
end
end
end
describe
"#to_be_resolved?"
do
context
"when not resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
false
)
end
it
"returns false"
do
expect
(
subject
.
to_be_resolved?
).
to
be
false
end
end
context
"when resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
true
)
end
context
"when resolved"
do
before
do
allow
(
subject
).
to
receive
(
:resolved?
).
and_return
(
true
)
end
it
"returns false"
do
expect
(
subject
.
to_be_resolved?
).
to
be
false
end
end
context
"when not resolved"
do
before
do
allow
(
subject
).
to
receive
(
:resolved?
).
and_return
(
false
)
end
it
"returns true"
do
expect
(
subject
.
to_be_resolved?
).
to
be
true
end
end
end
end
describe
"#resolved?"
do
# TODO: Test
end
describe
"#resolve!"
do
let
(
:current_user
)
{
create
(
:user
)
}
context
"when not resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
false
)
end
it
"returns nil"
do
expect
(
subject
.
resolve!
(
current_user
)).
to
be_nil
end
it
"doesn't set resolved_at"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved_at
).
to
be_nil
end
it
"doesn't set resolved_by"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved_by
).
to
be_nil
end
it
"doesn't mark as resolved"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved?
).
to
be
false
end
end
context
"when resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
true
)
end
context
"when already resolved"
do
let
(
:user
)
{
create
(
:user
)
}
before
do
subject
.
resolve!
(
user
)
end
it
"returns nil"
do
expect
(
subject
.
resolve!
(
current_user
)).
to
be_nil
end
it
"doesn't change resolved_at"
do
expect
(
subject
.
resolved_at
).
not_to
be_nil
expect
{
subject
.
resolve!
(
current_user
)
}.
not_to
change
{
subject
.
resolved_at
}
end
it
"doesn't change resolved_by"
do
expect
(
subject
.
resolved_by
).
to
eq
(
user
)
expect
{
subject
.
resolve!
(
current_user
)
}.
not_to
change
{
subject
.
resolved_by
}
end
it
"doesn't change resolved status"
do
expect
(
subject
.
resolved?
).
to
be
true
expect
{
subject
.
resolve!
(
current_user
)
}.
not_to
change
{
subject
.
resolved?
}
end
end
context
"when not yet resolved"
do
it
"returns true"
do
expect
(
subject
.
resolve!
(
current_user
)).
to
be
true
end
it
"sets resolved_at"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved_at
).
not_to
be_nil
end
it
"sets resolved_by"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved_by
).
to
eq
(
current_user
)
end
it
"marks as resolved"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved?
).
to
be
true
end
end
end
end
describe
"#unresolve!"
do
context
"when not resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
false
)
end
it
"returns nil"
do
expect
(
subject
.
unresolve!
).
to
be_nil
end
end
context
"when resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
true
)
end
context
"when resolved"
do
let
(
:user
)
{
create
(
:user
)
}
before
do
subject
.
resolve!
(
user
)
end
it
"returns true"
do
expect
(
subject
.
unresolve!
).
to
be
true
end
it
"unsets resolved_at"
do
subject
.
unresolve!
expect
(
subject
.
resolved_at
).
to
be_nil
end
it
"unsets resolved_by"
do
subject
.
unresolve!
expect
(
subject
.
resolved_by
).
to
be_nil
end
it
"unmarks as resolved"
do
subject
.
unresolve!
expect
(
subject
.
resolved?
).
to
be
false
end
end
context
"when not resolved"
do
it
"returns nil"
do
expect
(
subject
.
unresolve!
).
to
be_nil
end
end
end
end
end
spec/models/diff_discussion_spec.rb
0 → 100644
View file @
08bbb9fc
require
'spec_helper'
describe
DiffDiscussion
,
model:
true
do
# TODO: Test
describe
"#truncated_diff_lines"
do
let
(
:truncated_lines
)
{
subject
.
truncated_diff_lines
}
context
"when diff is greater than allowed number of truncated diff lines "
do
it
"returns fewer lines"
do
expect
(
subject
.
diff_lines
.
count
).
to
be
>
described_class
::
NUMBER_OF_TRUNCATED_DIFF_LINES
expect
(
truncated_lines
.
count
).
to
be
<=
described_class
::
NUMBER_OF_TRUNCATED_DIFF_LINES
end
end
context
"when some diff lines are meta"
do
it
"returns no meta lines"
do
expect
(
subject
.
diff_lines
).
to
include
(
be_meta
)
expect
(
truncated_lines
).
not_to
include
(
be_meta
)
end
end
end
end
spec/models/diff_note_spec.rb
View file @
08bbb9fc
...
@@ -31,43 +31,6 @@ describe DiffNote, models: true do
...
@@ -31,43 +31,6 @@ describe DiffNote, models: true do
subject
{
create
(
:diff_note_on_merge_request
,
project:
project
,
position:
position
,
noteable:
merge_request
)
}
subject
{
create
(
:diff_note_on_merge_request
,
project:
project
,
position:
position
,
noteable:
merge_request
)
}
describe
".resolve!"
do
let
(
:current_user
)
{
create
(
:user
)
}
let!
(
:commit_note
)
{
create
(
:diff_note_on_commit
)
}
let!
(
:resolved_note
)
{
create
(
:diff_note_on_merge_request
,
:resolved
)
}
let!
(
:unresolved_note
)
{
create
(
:diff_note_on_merge_request
)
}
before
do
described_class
.
resolve!
(
current_user
)
commit_note
.
reload
resolved_note
.
reload
unresolved_note
.
reload
end
it
'resolves only the resolvable, not yet resolved notes'
do
expect
(
commit_note
.
resolved_at
).
to
be_nil
expect
(
resolved_note
.
resolved_by
).
not_to
eq
(
current_user
)
expect
(
unresolved_note
.
resolved_at
).
not_to
be_nil
expect
(
unresolved_note
.
resolved_by
).
to
eq
(
current_user
)
end
end
describe
".unresolve!"
do
let!
(
:resolved_note
)
{
create
(
:diff_note_on_merge_request
,
:resolved
)
}
before
do
described_class
.
unresolve!
resolved_note
.
reload
end
it
'unresolves the resolved notes'
do
expect
(
resolved_note
.
resolved_by
).
to
be_nil
expect
(
resolved_note
.
resolved_at
).
to
be_nil
end
end
describe
"#position="
do
describe
"#position="
do
context
"when provided a string"
do
context
"when provided a string"
do
it
"sets the position"
do
it
"sets the position"
do
...
@@ -94,6 +57,10 @@ describe DiffNote, models: true do
...
@@ -94,6 +57,10 @@ describe DiffNote, models: true do
end
end
end
end
describe
"#original_position="
do
# TODO: Test
end
describe
"#diff_file"
do
describe
"#diff_file"
do
it
"returns the correct diff file"
do
it
"returns the correct diff file"
do
diff_file
=
subject
.
diff_file
diff_file
=
subject
.
diff_file
...
@@ -226,252 +193,6 @@ describe DiffNote, models: true do
...
@@ -226,252 +193,6 @@ describe DiffNote, models: true do
end
end
end
end
describe
"#resolvable?"
do
context
"when noteable is a commit"
do
subject
{
create
(
:diff_note_on_commit
,
project:
project
,
position:
position
)
}
it
"returns false"
do
expect
(
subject
.
resolvable?
).
to
be
false
end
end
context
"when noteable is a merge request"
do
context
"when a system note"
do
before
do
subject
.
system
=
true
end
it
"returns false"
do
expect
(
subject
.
resolvable?
).
to
be
false
end
end
context
"when a regular note"
do
it
"returns true"
do
expect
(
subject
.
resolvable?
).
to
be
true
end
end
end
end
describe
"#to_be_resolved?"
do
context
"when not resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
false
)
end
it
"returns false"
do
expect
(
subject
.
to_be_resolved?
).
to
be
false
end
end
context
"when resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
true
)
end
context
"when resolved"
do
before
do
allow
(
subject
).
to
receive
(
:resolved?
).
and_return
(
true
)
end
it
"returns false"
do
expect
(
subject
.
to_be_resolved?
).
to
be
false
end
end
context
"when not resolved"
do
before
do
allow
(
subject
).
to
receive
(
:resolved?
).
and_return
(
false
)
end
it
"returns true"
do
expect
(
subject
.
to_be_resolved?
).
to
be
true
end
end
end
end
describe
"#resolve!"
do
let
(
:current_user
)
{
create
(
:user
)
}
context
"when not resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
false
)
end
it
"returns nil"
do
expect
(
subject
.
resolve!
(
current_user
)).
to
be_nil
end
it
"doesn't set resolved_at"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved_at
).
to
be_nil
end
it
"doesn't set resolved_by"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved_by
).
to
be_nil
end
it
"doesn't mark as resolved"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved?
).
to
be
false
end
end
context
"when resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
true
)
end
context
"when already resolved"
do
let
(
:user
)
{
create
(
:user
)
}
before
do
subject
.
resolve!
(
user
)
end
it
"returns nil"
do
expect
(
subject
.
resolve!
(
current_user
)).
to
be_nil
end
it
"doesn't change resolved_at"
do
expect
(
subject
.
resolved_at
).
not_to
be_nil
expect
{
subject
.
resolve!
(
current_user
)
}.
not_to
change
{
subject
.
resolved_at
}
end
it
"doesn't change resolved_by"
do
expect
(
subject
.
resolved_by
).
to
eq
(
user
)
expect
{
subject
.
resolve!
(
current_user
)
}.
not_to
change
{
subject
.
resolved_by
}
end
it
"doesn't change resolved status"
do
expect
(
subject
.
resolved?
).
to
be
true
expect
{
subject
.
resolve!
(
current_user
)
}.
not_to
change
{
subject
.
resolved?
}
end
end
context
"when not yet resolved"
do
it
"returns true"
do
expect
(
subject
.
resolve!
(
current_user
)).
to
be
true
end
it
"sets resolved_at"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved_at
).
not_to
be_nil
end
it
"sets resolved_by"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved_by
).
to
eq
(
current_user
)
end
it
"marks as resolved"
do
subject
.
resolve!
(
current_user
)
expect
(
subject
.
resolved?
).
to
be
true
end
end
end
end
describe
"#unresolve!"
do
context
"when not resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
false
)
end
it
"returns nil"
do
expect
(
subject
.
unresolve!
).
to
be_nil
end
end
context
"when resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
true
)
end
context
"when resolved"
do
let
(
:user
)
{
create
(
:user
)
}
before
do
subject
.
resolve!
(
user
)
end
it
"returns true"
do
expect
(
subject
.
unresolve!
).
to
be
true
end
it
"unsets resolved_at"
do
subject
.
unresolve!
expect
(
subject
.
resolved_at
).
to
be_nil
end
it
"unsets resolved_by"
do
subject
.
unresolve!
expect
(
subject
.
resolved_by
).
to
be_nil
end
it
"unmarks as resolved"
do
subject
.
unresolve!
expect
(
subject
.
resolved?
).
to
be
false
end
end
context
"when not resolved"
do
it
"returns nil"
do
expect
(
subject
.
unresolve!
).
to
be_nil
end
end
end
end
describe
"#discussion"
do
context
"when not resolvable"
do
before
do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
false
)
end
it
"returns nil"
do
expect
(
subject
.
discussion
).
to
be_nil
end
end
context
"when resolvable"
do
let!
(
:diff_note2
)
{
create
(
:diff_note_on_merge_request
,
project:
project
,
noteable:
merge_request
,
position:
subject
.
position
)
}
let!
(
:diff_note3
)
{
create
(
:diff_note_on_merge_request
,
project:
project
,
noteable:
merge_request
,
position:
active_position2
)
}
let
(
:active_position2
)
do
Gitlab
::
Diff
::
Position
.
new
(
old_path:
"files/ruby/popen.rb"
,
new_path:
"files/ruby/popen.rb"
,
old_line:
16
,
new_line:
22
,
diff_refs:
merge_request
.
diff_refs
)
end
it
"returns the discussion this note is in"
do
discussion
=
subject
.
discussion
expect
(
discussion
.
id
).
to
eq
(
subject
.
discussion_id
)
expect
(
discussion
.
notes
).
to
eq
([
subject
,
diff_note2
])
end
end
end
describe
"#discussion_id"
do
describe
"#discussion_id"
do
let
(
:note
)
{
create
(
:diff_note_on_merge_request
)
}
let
(
:note
)
{
create
(
:diff_note_on_merge_request
)
}
...
...
spec/models/discussion_note_spec.rb
0 → 100644
View file @
08bbb9fc
require
'spec_helper'
describe
DiscussionNote
,
models:
true
do
# TODO: Test
end
spec/models/discussion_spec.rb
View file @
08bbb9fc
...
@@ -3,14 +3,34 @@ require 'spec_helper'
...
@@ -3,14 +3,34 @@ require 'spec_helper'
describe
Discussion
,
model:
true
do
describe
Discussion
,
model:
true
do
subject
{
described_class
.
new
([
first_note
,
second_note
,
third_note
])
}
subject
{
described_class
.
new
([
first_note
,
second_note
,
third_note
])
}
let
(
:first_note
)
{
create
(
:diff_note_on_merge_request
)
}
let
(
:first_note
)
{
create
(
:discussion_note_on_merge_request
)
}
let
(
:second_note
)
{
create
(
:diff_note_on_merge_request
)
}
let
(
:second_note
)
{
create
(
:discussion_note_on_merge_request
)
}
let
(
:third_note
)
{
create
(
:diff_note_on_merge_request
)
}
let
(
:third_note
)
{
create
(
:discussion_note_on_merge_request
)
}
describe
'.build'
do
# TODO: Test
end
describe
'.build_collection'
do
# TODO: Test
end
describe
'#id'
do
# TODO: Test
end
describe
'#original_id'
do
# TODO: Test
end
describe
'#potentially_resolvable?'
do
# TODO: Test
end
describe
"#resolvable?"
do
describe
"#resolvable?"
do
context
"when
a diff discussion
"
do
context
"when
potentially resolvable
"
do
before
do
before
do
allow
(
subject
).
to
receive
(
:di
ff_discussion
?
).
and_return
(
true
)
allow
(
subject
).
to
receive
(
:di
scussion_resolvable
?
).
and_return
(
true
)
end
end
context
"when all notes are unresolvable"
do
context
"when all notes are unresolvable"
do
...
@@ -50,9 +70,9 @@ describe Discussion, model: true do
...
@@ -50,9 +70,9 @@ describe Discussion, model: true do
end
end
end
end
context
"when not
a diff discussion
"
do
context
"when not
potentially resolvable
"
do
before
do
before
do
allow
(
subject
).
to
receive
(
:di
ff_discussion
?
).
and_return
(
false
)
allow
(
subject
).
to
receive
(
:di
scussion_resolvable
?
).
and_return
(
false
)
end
end
it
"returns false"
do
it
"returns false"
do
...
@@ -530,10 +550,14 @@ describe Discussion, model: true do
...
@@ -530,10 +550,14 @@ describe Discussion, model: true do
end
end
end
end
describe
"#last_resolved_note"
do
# TODO: Test
end
describe
"#collapsed?"
do
describe
"#collapsed?"
do
context
"when
a diff discussion
"
do
context
"when
potentially resolvable
"
do
before
do
before
do
allow
(
subject
).
to
receive
(
:di
ff_discussion
?
).
and_return
(
true
)
allow
(
subject
).
to
receive
(
:di
scussion_resolvable
?
).
and_return
(
true
)
end
end
context
"when resolvable"
do
context
"when resolvable"
do
...
@@ -567,31 +591,43 @@ describe Discussion, model: true do
...
@@ -567,31 +591,43 @@ describe Discussion, model: true do
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
false
)
allow
(
subject
).
to
receive
(
:resolvable?
).
and_return
(
false
)
end
end
context
"when a
ctive
"
do
context
"when a
diff discussion
"
do
before
do
before
do
allow
(
subject
).
to
receive
(
:
active
?
).
and_return
(
true
)
allow
(
subject
).
to
receive
(
:
diff_discussion
?
).
and_return
(
true
)
end
end
it
"returns false"
do
context
"when active"
do
expect
(
subject
.
collapsed?
).
to
be
false
before
do
allow
(
subject
).
to
receive
(
:active?
).
and_return
(
true
)
end
it
"returns false"
do
expect
(
subject
.
collapsed?
).
to
be
false
end
end
end
end
context
"when outdated"
do
context
"when outdated"
do
before
do
before
do
allow
(
subject
).
to
receive
(
:active?
).
and_return
(
false
)
allow
(
subject
).
to
receive
(
:active?
).
and_return
(
false
)
end
it
"returns true"
do
expect
(
subject
.
collapsed?
).
to
be
true
end
end
end
end
it
"returns true"
do
context
"when not a diff discussion"
do
expect
(
subject
.
collapsed?
).
to
be
true
it
"returns false"
do
expect
(
subject
.
collapsed?
).
to
be
false
end
end
end
end
end
end
end
end
context
"when not
a diff discussion
"
do
context
"when not
potentially resolvable
"
do
before
do
before
do
allow
(
subject
).
to
receive
(
:di
ff_discussion
?
).
and_return
(
false
)
allow
(
subject
).
to
receive
(
:di
scussion_resolvable
?
).
and_return
(
false
)
end
end
it
"returns false"
do
it
"returns false"
do
...
@@ -599,23 +635,4 @@ describe Discussion, model: true do
...
@@ -599,23 +635,4 @@ describe Discussion, model: true do
end
end
end
end
end
end
describe
"#truncated_diff_lines"
do
let
(
:truncated_lines
)
{
subject
.
truncated_diff_lines
}
context
"when diff is greater than allowed number of truncated diff lines "
do
it
"returns fewer lines"
do
expect
(
subject
.
diff_lines
.
count
).
to
be
>
described_class
::
NUMBER_OF_TRUNCATED_DIFF_LINES
expect
(
truncated_lines
.
count
).
to
be
<=
described_class
::
NUMBER_OF_TRUNCATED_DIFF_LINES
end
end
context
"when some diff lines are meta"
do
it
"returns no meta lines"
do
expect
(
subject
.
diff_lines
).
to
include
(
be_meta
)
expect
(
truncated_lines
).
not_to
include
(
be_meta
)
end
end
end
end
end
spec/models/individual_note_discussion_spec.rb
0 → 100644
View file @
08bbb9fc
require
'spec_helper'
describe
IndividualNoteDiscussion
,
models:
true
do
# TODO: Test
end
spec/models/legacy_diff_discussion_spec.rb
0 → 100644
View file @
08bbb9fc
require
'spec_helper'
describe
LegacyDiffDiscussion
,
models:
true
do
# TODO: Test
end
spec/models/legacy_diff_note_spec.rb
deleted
100644 → 0
View file @
8bdfee8b
require
'spec_helper'
describe
LegacyDiffNote
,
models:
true
do
describe
"Commit diff line notes"
do
let!
(
:note
)
{
create
(
:legacy_diff_note_on_commit
,
note:
"+1 from me"
)
}
let!
(
:commit
)
{
note
.
noteable
}
it
"saves a valid note"
do
expect
(
note
.
commit_id
).
to
eq
(
commit
.
id
)
expect
(
note
.
noteable
.
id
).
to
eq
(
commit
.
id
)
end
it
"is recognized by #legacy_diff_note?"
do
expect
(
note
).
to
be_legacy_diff_note
end
end
describe
'#active?'
do
it
'is always true when the note has no associated diff line'
do
note
=
build
(
:legacy_diff_note_on_merge_request
)
expect
(
note
).
to
receive
(
:diff_line
).
and_return
(
nil
)
expect
(
note
).
to
be_active
end
it
'is never true when the note has no noteable associated'
do
note
=
build
(
:legacy_diff_note_on_merge_request
)
expect
(
note
).
to
receive
(
:diff_line
).
and_return
(
double
)
expect
(
note
).
to
receive
(
:noteable
).
and_return
(
nil
)
expect
(
note
).
not_to
be_active
end
it
'returns the memoized value if defined'
do
note
=
build
(
:legacy_diff_note_on_merge_request
)
note
.
instance_variable_set
(
:@active
,
'foo'
)
expect
(
note
).
not_to
receive
(
:find_noteable_diff
)
expect
(
note
.
active?
).
to
eq
'foo'
end
context
'for a merge request noteable'
do
it
'is false when noteable has no matching diff'
do
merge
=
build_stubbed
(
:merge_request
,
:simple
)
note
=
build
(
:legacy_diff_note_on_merge_request
,
noteable:
merge
)
allow
(
note
).
to
receive
(
:diff_line
).
and_return
(
double
)
expect
(
note
).
to
receive
(
:find_noteable_diff
).
and_return
(
nil
)
expect
(
note
).
not_to
be_active
end
it
'is true when noteable has a matching diff'
do
merge
=
create
(
:merge_request
,
:simple
)
# Generate a real line_code value so we know it will match. We use a
# random line from a random diff just for funsies.
diff
=
merge
.
raw_diffs
.
to_a
.
sample
line
=
Gitlab
::
Diff
::
Parser
.
new
.
parse
(
diff
.
diff
.
each_line
).
to_a
.
sample
code
=
Gitlab
::
Diff
::
LineCode
.
generate
(
diff
.
new_path
,
line
.
new_pos
,
line
.
old_pos
)
# We're persisting in order to trigger the set_diff callback
note
=
create
(
:legacy_diff_note_on_merge_request
,
noteable:
merge
,
line_code:
code
,
project:
merge
.
source_project
)
# Make sure we don't get a false positive from a guard clause
expect
(
note
).
to
receive
(
:find_noteable_diff
).
and_call_original
expect
(
note
).
to
be_active
end
end
end
describe
"#discussion_id"
do
let
(
:note
)
{
create
(
:note
)
}
context
"when it is newly created"
do
it
"has a discussion id"
do
expect
(
note
.
discussion_id
).
not_to
be_nil
expect
(
note
.
discussion_id
).
to
match
(
/\A\h{40}\z/
)
end
end
context
"when it didn't store a discussion id before"
do
before
do
note
.
update_column
(
:discussion_id
,
nil
)
end
it
"has a discussion id"
do
# The discussion_id is set in `after_initialize`, so `reload` won't work
reloaded_note
=
Note
.
find
(
note
.
id
)
expect
(
reloaded_note
.
discussion_id
).
not_to
be_nil
expect
(
reloaded_note
.
discussion_id
).
to
match
(
/\A\h{40}\z/
)
end
end
end
end
spec/models/merge_request_spec.rb
View file @
08bbb9fc
...
@@ -1225,12 +1225,12 @@ describe MergeRequest, models: true do
...
@@ -1225,12 +1225,12 @@ describe MergeRequest, models: true do
end
end
context
"discussion status"
do
context
"discussion status"
do
let
(
:first_discussion
)
{
Discussion
.
new
([
create
(
:di
ff
_note_on_merge_request
)])
}
let
(
:first_discussion
)
{
Discussion
.
new
([
create
(
:di
scussion
_note_on_merge_request
)])
}
let
(
:second_discussion
)
{
Discussion
.
new
([
create
(
:di
ff
_note_on_merge_request
)])
}
let
(
:second_discussion
)
{
Discussion
.
new
([
create
(
:di
scussion
_note_on_merge_request
)])
}
let
(
:third_discussion
)
{
Discussion
.
new
([
create
(
:di
ff
_note_on_merge_request
)])
}
let
(
:third_discussion
)
{
Discussion
.
new
([
create
(
:di
scussion
_note_on_merge_request
)])
}
before
do
before
do
allow
(
subject
).
to
receive
(
:
diff
_discussions
).
and_return
([
first_discussion
,
second_discussion
,
third_discussion
])
allow
(
subject
).
to
receive
(
:
resolvable
_discussions
).
and_return
([
first_discussion
,
second_discussion
,
third_discussion
])
end
end
describe
'#resolvable_discussions'
do
describe
'#resolvable_discussions'
do
...
@@ -1245,34 +1245,6 @@ describe MergeRequest, models: true do
...
@@ -1245,34 +1245,6 @@ describe MergeRequest, models: true do
end
end
end
end
describe
'#discussions_can_be_resolved_by? user'
do
let
(
:user
)
{
build
(
:user
)
}
context
'all discussions can be resolved by the user'
do
before
do
allow
(
first_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
true
)
allow
(
second_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
true
)
allow
(
third_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
true
)
end
it
'allows a user to resolve the discussions'
do
expect
(
subject
.
discussions_can_be_resolved_by?
(
user
)).
to
be
(
true
)
end
end
context
'one discussion cannot be resolved by the user'
do
before
do
allow
(
first_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
true
)
allow
(
second_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
true
)
allow
(
third_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
false
)
end
it
'allows a user to resolve the discussions'
do
expect
(
subject
.
discussions_can_be_resolved_by?
(
user
)).
to
be
(
false
)
end
end
end
describe
"#discussions_resolvable?"
do
describe
"#discussions_resolvable?"
do
context
"when all discussions are unresolvable"
do
context
"when all discussions are unresolvable"
do
before
do
before
do
...
@@ -1398,6 +1370,38 @@ describe MergeRequest, models: true do
...
@@ -1398,6 +1370,38 @@ describe MergeRequest, models: true do
end
end
end
end
end
end
describe
"#discussions_to_be_resolved"
do
# TODO: Test
end
describe
'#discussions_can_be_resolved_by?'
do
let
(
:user
)
{
build
(
:user
)
}
context
'all discussions can be resolved by the user'
do
before
do
allow
(
first_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
true
)
allow
(
second_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
true
)
allow
(
third_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
true
)
end
it
'allows a user to resolve the discussions'
do
expect
(
subject
.
discussions_can_be_resolved_by?
(
user
)).
to
be
(
true
)
end
end
context
'one discussion cannot be resolved by the user'
do
before
do
allow
(
first_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
true
)
allow
(
second_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
true
)
allow
(
third_discussion
).
to
receive
(
:can_resolve?
).
with
(
user
).
and_return
(
false
)
end
it
'allows a user to resolve the discussions'
do
expect
(
subject
.
discussions_can_be_resolved_by?
(
user
)).
to
be
(
false
)
end
end
end
end
end
describe
'#conflicts_can_be_resolved_in_ui?'
do
describe
'#conflicts_can_be_resolved_in_ui?'
do
...
...
spec/models/note_spec.rb
View file @
08bbb9fc
...
@@ -245,6 +245,18 @@ describe Note, models: true do
...
@@ -245,6 +245,18 @@ describe Note, models: true do
end
end
end
end
describe
'.discussions'
do
# TODO: Test
end
describe
'.find_original_discussion'
do
# TODO: Test
end
describe
'.find_discussion'
do
# TODO: Test
end
describe
".grouped_diff_discussions"
do
describe
".grouped_diff_discussions"
do
let!
(
:merge_request
)
{
create
(
:merge_request
)
}
let!
(
:merge_request
)
{
create
(
:merge_request
)
}
let
(
:project
)
{
merge_request
.
project
}
let
(
:project
)
{
merge_request
.
project
}
...
@@ -297,31 +309,6 @@ describe Note, models: true do
...
@@ -297,31 +309,6 @@ describe Note, models: true do
end
end
end
end
describe
"#discussion_id"
do
let
(
:note
)
{
create
(
:note
)
}
context
"when it is newly created"
do
it
"has a discussion id"
do
expect
(
note
.
discussion_id
).
not_to
be_nil
expect
(
note
.
discussion_id
).
to
match
(
/\A\h{40}\z/
)
end
end
context
"when it didn't store a discussion id before"
do
before
do
note
.
update_column
(
:discussion_id
,
nil
)
end
it
"has a discussion id"
do
# The discussion_id is set in `after_initialize`, so `reload` won't work
reloaded_note
=
Note
.
find
(
note
.
id
)
expect
(
reloaded_note
.
discussion_id
).
not_to
be_nil
expect
(
reloaded_note
.
discussion_id
).
to
match
(
/\A\h{40}\z/
)
end
end
end
describe
'#for_personal_snippet?'
do
describe
'#for_personal_snippet?'
do
it
'returns false for a project snippet note'
do
it
'returns false for a project snippet note'
do
expect
(
build
(
:note_on_project_snippet
).
for_personal_snippet?
).
to
be_falsy
expect
(
build
(
:note_on_project_snippet
).
for_personal_snippet?
).
to
be_falsy
...
@@ -388,6 +375,86 @@ describe Note, models: true do
...
@@ -388,6 +375,86 @@ describe Note, models: true do
end
end
end
end
describe
'#can_be_discussion_note?'
do
# TODO: Test
end
describe
'#discussion_class'
do
# TODO: Test
end
describe
"#discussion_id"
do
let
(
:note
)
{
create
(
:note
)
}
context
"when it is newly created"
do
it
"has a discussion id"
do
expect
(
note
.
discussion_id
).
not_to
be_nil
expect
(
note
.
discussion_id
).
to
match
(
/\A\h{40}\z/
)
end
end
context
"when it didn't store a discussion id before"
do
before
do
note
.
update_column
(
:discussion_id
,
nil
)
end
it
"has a discussion id"
do
# The discussion_id is set in `after_initialize`, so `reload` won't work
reloaded_note
=
Note
.
find
(
note
.
id
)
expect
(
reloaded_note
.
discussion_id
).
not_to
be_nil
expect
(
reloaded_note
.
discussion_id
).
to
match
(
/\A\h{40}\z/
)
end
end
end
describe
"#original_discussion_id"
do
# TODO: Test
end
describe
'#to_discussion'
do
subject
{
create
(
:discussion_note_on_merge_request
)
}
let!
(
:note2
)
{
create
(
:discussion_note_on_merge_request
,
project:
subject
.
project
,
noteable:
subject
.
noteable
,
in_reply_to_discussion_id:
subject
.
discussion_id
)
}
it
"returns a discussion with just this note"
do
discussion
=
subject
.
to_discussion
expect
(
discussion
.
id
).
to
eq
(
subject
.
discussion_id
)
expect
(
discussion
.
notes
).
to
eq
([
subject
])
end
end
describe
"#discussion"
do
let!
(
:note1
)
{
create
(
:discussion_note_on_merge_request
)
}
let!
(
:note2
)
{
create
(
:diff_note_on_merge_request
,
project:
note1
.
project
,
noteable:
note1
.
noteable
)
}
context
'when the note is part of a discussion'
do
subject
{
create
(
:discussion_note_on_merge_request
,
project:
note1
.
project
,
noteable:
note1
.
noteable
,
in_reply_to_discussion_id:
note1
.
discussion_id
)
}
it
"returns the discussion this note is in"
do
discussion
=
subject
.
discussion
expect
(
discussion
.
id
).
to
eq
(
subject
.
discussion_id
)
expect
(
discussion
.
notes
).
to
eq
([
note1
,
subject
])
end
end
context
'when the note is not part of a discussion'
do
subject
{
create
(
:note
)
}
it
"returns a discussion with just this note"
do
discussion
=
subject
.
discussion
expect
(
discussion
.
id
).
to
eq
(
subject
.
discussion_id
)
expect
(
discussion
.
notes
).
to
eq
([
subject
])
end
end
end
describe
"#part_of_discussion?"
do
# TODO: Test
end
describe
'expiring ETag cache'
do
describe
'expiring ETag cache'
do
let
(
:note
)
{
build
(
:note_on_issue
)
}
let
(
:note
)
{
build
(
:note_on_issue
)
}
...
...
spec/models/sent_notification_spec.rb
0 → 100644
View file @
08bbb9fc
require
'spec_helper'
describe
SentNotification
,
model:
true
do
# TODO: Test
end
spec/models/simple_discussion_spec.rb
0 → 100644
View file @
08bbb9fc
require
'spec_helper'
describe
SimpleDiscussion
,
model:
true
do
# TODO: Test
end
spec/requests/api/v3/issues_spec.rb
View file @
08bbb9fc
...
@@ -824,7 +824,7 @@ describe API::V3::Issues, api: true do
...
@@ -824,7 +824,7 @@ describe API::V3::Issues, api: true do
end
end
context
'resolving issues in a merge request'
do
context
'resolving issues in a merge request'
do
let
(
:discussion
)
{
Discussion
.
for_diff_notes
([
create
(
:diff_note_on_merge_request
)]).
first
}
let
(
:discussion
)
{
create
(
:diff_note_on_merge_request
).
to_discussion
}
let
(
:merge_request
)
{
discussion
.
noteable
}
let
(
:merge_request
)
{
discussion
.
noteable
}
let
(
:project
)
{
merge_request
.
source_project
}
let
(
:project
)
{
merge_request
.
source_project
}
before
do
before
do
...
...
spec/services/discussions/resolve_service_spec.rb
View file @
08bbb9fc
...
@@ -2,7 +2,7 @@ require 'spec_helper'
...
@@ -2,7 +2,7 @@ require 'spec_helper'
describe
Discussions
::
ResolveService
do
describe
Discussions
::
ResolveService
do
describe
'#execute'
do
describe
'#execute'
do
let
(
:discussion
)
{
Discussion
.
for_diff_notes
([
create
(
:diff_note_on_merge_request
)]).
first
}
let
(
:discussion
)
{
create
(
:diff_note_on_merge_request
).
to_discussion
}
let
(
:project
)
{
merge_request
.
project
}
let
(
:project
)
{
merge_request
.
project
}
let
(
:merge_request
)
{
discussion
.
noteable
}
let
(
:merge_request
)
{
discussion
.
noteable
}
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
...
@@ -41,7 +41,7 @@ describe Discussions::ResolveService do
...
@@ -41,7 +41,7 @@ describe Discussions::ResolveService do
end
end
it
'can resolve multiple discussions at once'
do
it
'can resolve multiple discussions at once'
do
other_discussion
=
Discussion
.
for_diff_notes
([
create
(
:diff_note_on_merge_request
,
noteable:
discussion
.
noteable
,
project:
discussion
.
noteable
.
source_project
)]).
first
other_discussion
=
create
(
:diff_note_on_merge_request
,
noteable:
discussion
.
noteable
,
project:
discussion
.
noteable
.
source_project
).
to_discussion
service
.
execute
([
discussion
,
other_discussion
])
service
.
execute
([
discussion
,
other_discussion
])
...
...
spec/services/issues/build_service_spec.rb
View file @
08bbb9fc
...
@@ -96,13 +96,13 @@ describe Issues::BuildService, services: true do
...
@@ -96,13 +96,13 @@ describe Issues::BuildService, services: true do
end
end
it
'mentions all the authors in the description'
do
it
'mentions all the authors in the description'
do
authors
=
merge_request
.
diff
_discussions
.
map
(
&
:author
)
authors
=
merge_request
.
resolvable
_discussions
.
map
(
&
:author
)
expect
(
issue
.
description
).
to
include
(
*
authors
.
map
(
&
:to_reference
))
expect
(
issue
.
description
).
to
include
(
*
authors
.
map
(
&
:to_reference
))
end
end
it
'has a link for each unresolved discussion in the description'
do
it
'has a link for each unresolved discussion in the description'
do
notes
=
merge_request
.
diff
_discussions
.
map
(
&
:first_note
)
notes
=
merge_request
.
resolvable
_discussions
.
map
(
&
:first_note
)
links
=
notes
.
map
{
|
note
|
Gitlab
::
UrlBuilder
.
build
(
note
)
}
links
=
notes
.
map
{
|
note
|
Gitlab
::
UrlBuilder
.
build
(
note
)
}
expect
(
issue
.
description
).
to
include
(
*
links
)
expect
(
issue
.
description
).
to
include
(
*
links
)
...
...
spec/services/issues/create_service_spec.rb
View file @
08bbb9fc
...
@@ -141,7 +141,7 @@ describe Issues::CreateService, services: true do
...
@@ -141,7 +141,7 @@ describe Issues::CreateService, services: true do
it_behaves_like
'new issuable record that supports slash commands'
it_behaves_like
'new issuable record that supports slash commands'
context
'resolving discussions'
do
context
'resolving discussions'
do
let
(
:discussion
)
{
Discussion
.
for_diff_notes
([
create
(
:diff_note_on_merge_request
)]).
first
}
let
(
:discussion
)
{
create
(
:diff_note_on_merge_request
).
to_discussion
}
let
(
:merge_request
)
{
discussion
.
noteable
}
let
(
:merge_request
)
{
discussion
.
noteable
}
let
(
:project
)
{
merge_request
.
source_project
}
let
(
:project
)
{
merge_request
.
source_project
}
...
...
spec/services/notes/build_service_spec.rb
0 → 100644
View file @
08bbb9fc
require
'spec_helper'
describe
Notes
::
CreateService
,
services:
true
do
# TODO: Test
end
spec/services/notes/create_service_spec.rb
View file @
08bbb9fc
...
@@ -13,6 +13,9 @@ describe Notes::CreateService, services: true do
...
@@ -13,6 +13,9 @@ describe Notes::CreateService, services: true do
project
.
team
<<
[
user
,
:master
]
project
.
team
<<
[
user
,
:master
]
end
end
# TODO: Test in_reply_to_discussion_id
# TODO: Test new_discussion
context
"valid params"
do
context
"valid params"
do
it
'returns a valid note'
do
it
'returns a valid note'
do
note
=
described_class
.
new
(
project
,
user
,
opts
).
execute
note
=
described_class
.
new
(
project
,
user
,
opts
).
execute
...
...
spec/services/notification_service_spec.rb
View file @
08bbb9fc
...
@@ -439,7 +439,7 @@ describe NotificationService, services: true do
...
@@ -439,7 +439,7 @@ describe NotificationService, services: true do
notification
.
new_note
(
note
)
notification
.
new_note
(
note
)
expect
(
SentNotification
.
last
.
position
).
to
eq
(
note
.
position
)
expect
(
SentNotification
.
last
.
in_reply_to_discussion_id
).
to
eq
(
note
.
original_discussion_id
)
end
end
end
end
end
end
...
...
spec/services/system_note_service_spec.rb
View file @
08bbb9fc
...
@@ -796,7 +796,7 @@ describe SystemNoteService, services: true do
...
@@ -796,7 +796,7 @@ describe SystemNoteService, services: true do
end
end
describe
'.discussion_continued_in_issue'
do
describe
'.discussion_continued_in_issue'
do
let
(
:discussion
)
{
Discussion
.
for_diff_notes
([
create
(
:diff_note_on_merge_request
)]).
first
}
let
(
:discussion
)
{
create
(
:diff_note_on_merge_request
).
to_discussion
}
let
(
:merge_request
)
{
discussion
.
noteable
}
let
(
:merge_request
)
{
discussion
.
noteable
}
let
(
:project
)
{
merge_request
.
source_project
}
let
(
:project
)
{
merge_request
.
source_project
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment