summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG5
-rw-r--r--app/assets/javascripts/awards_handler.coffee91
-rw-r--r--app/assets/javascripts/blob/blob_file_dropzone.js.coffee17
-rw-r--r--app/assets/javascripts/copy_to_clipboard.js.coffee21
-rw-r--r--app/assets/javascripts/new_commit_form.js.coffee21
-rw-r--r--app/assets/javascripts/notes.js.coffee9
-rw-r--r--app/assets/stylesheets/pages/issuable.scss68
-rw-r--r--app/controllers/ci/lints_controller.rb4
-rw-r--r--app/controllers/concerns/creates_merge_request_for_commit.rb28
-rw-r--r--app/controllers/projects/blob_controller.rb87
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/controllers/projects/notes_controller.rb26
-rw-r--r--app/controllers/projects/tree_controller.rb13
-rw-r--r--app/controllers/snippets_controller.rb9
-rw-r--r--app/finders/issuable_finder.rb20
-rw-r--r--app/finders/notes_finder.rb4
-rw-r--r--app/helpers/issues_helper.rb25
-rw-r--r--app/helpers/merge_requests_helper.rb8
-rw-r--r--app/models/ability.rb82
-rw-r--r--app/models/ci/commit.rb8
-rw-r--r--app/models/concerns/issuable.rb52
-rw-r--r--app/models/merge_request.rb3
-rw-r--r--app/models/note.rb48
-rw-r--r--app/services/git_push_service.rb12
-rw-r--r--app/services/notes/create_service.rb17
-rw-r--r--app/services/notification_service.rb1
-rw-r--r--app/views/projects/_zen.html.haml9
-rw-r--r--app/views/projects/blob/_actions.html.haml2
-rw-r--r--app/views/projects/blob/_new_dir.html.haml14
-rw-r--r--app/views/projects/blob/_remove.html.haml16
-rw-r--r--app/views/projects/blob/_upload.html.haml18
-rw-r--r--app/views/projects/blob/edit.html.haml11
-rw-r--r--app/views/projects/blob/new.html.haml16
-rw-r--r--app/views/projects/blob/show.html.haml4
-rw-r--r--app/views/projects/issues/_discussion.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml6
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml20
-rw-r--r--app/views/projects/releases/edit.html.haml3
-rw-r--r--app/views/projects/tags/new.html.haml13
-rw-r--r--app/views/projects/tree/_tree_content.html.haml2
-rw-r--r--app/views/shared/_commit_message_container.html.haml8
-rw-r--r--app/views/shared/_confirm_modal.html.haml2
-rw-r--r--app/views/shared/_new_commit_form.html.haml18
-rw-r--r--app/views/votes/_votes_block.html.haml42
-rw-r--r--app/views/votes/_votes_inline.html.haml9
-rw-r--r--config/routes.rb4
-rw-r--r--db/migrate/20151106000015_add_is_award_to_notes.rb6
-rw-r--r--db/migrate/20151109134526_add_issues_state_index.rb5
-rw-r--r--db/migrate/20151109134916_add_projects_visibility_level_index.rb5
-rw-r--r--db/schema.rb8
-rw-r--r--doc/api/merge_requests.md12
-rw-r--r--doc/api/notes.md8
-rw-r--r--features/project/issues/award_emoji.feature14
-rw-r--r--features/project/source/browse_files.feature14
-rw-r--r--features/steps/project/issues/award_emoji.rb41
-rw-r--r--features/steps/project/source/browse_files.rb30
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/award_emoji.rb12
-rw-r--r--spec/benchmarks/finders/issues_finder_spec.rb55
-rw-r--r--spec/controllers/snippets_controller_spec.rb118
-rw-r--r--spec/factories.rb12
-rw-r--r--spec/helpers/issues_helper_spec.rb26
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb6
-rw-r--r--spec/lib/votes_spec.rb188
-rw-r--r--spec/models/note_spec.rb87
-rw-r--r--spec/models/project_spec.rb11
-rw-r--r--spec/services/ci/create_commit_service_spec.rb24
-rw-r--r--spec/services/notes/create_service_spec.rb34
71 files changed, 964 insertions, 662 deletions
diff --git a/CHANGELOG b/CHANGELOG
index ea8d5d6000f..75e5b7585f9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -6,6 +6,7 @@ v 8.2.0
- Improved performance of finding projects and groups in various places
- Improved performance of rendering user profile pages and Atom feeds
- Fix grouping of contributors by email in graph.
+ - Improved performance of finding issues with/without labels
- Remove CSS property preventing hard tabs from rendering in Chromium 45 (Stan Hu)
- Fix Drone CI service template not saving properly (Stan Hu)
- Fix avatars not showing in Atom feeds and project issues when Gravatar disabled (Stan Hu)
@@ -15,6 +16,7 @@ v 8.2.0
- Add allow_failure field to commit status API (Stan Hu)
- Commits without .gitlab-ci.yml are marked as skipped
- Save detailed error when YAML syntax is invalid
+ - Since GitLab CI is enabled by default, remove enabling it by pushing .gitlab-ci.yml
- Added build artifacts
- Improved performance of replacing references in comments
- Show last project commit to default branch on project home page
@@ -33,6 +35,7 @@ v 8.2.0
- Allow to define cache in `.gitlab-ci.yml`
- Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu)
- Remove deprecated CI events from project settings page
+ - Improve personal snippet access workflow (Douglas Alexandre)
- [API] Add ability to fetch the commit ID of the last commit that actually touched a file
- Fix omniauth documentation setting for omnibus configuration (Jon Cairns)
- Add "New file" link to dropdown on project page
@@ -54,7 +57,9 @@ v 8.2.0
- Fix trailing whitespace issue in merge request/issue title
- Fix bug when milestone/label filter was empty for dashboard issues page
- Add ability to create milestone in group projects from single form
+ - Add option to create merge request when editing/creating a file (Dirceu Tiegs)
- Prevent the last owner of a group from being able to delete themselves by 'adding' themselves as a master (James Lopez)
+ - Add Award Emoji to issue and merge request pages
v 8.1.4
- Fix bug where manually merged branches in a MR would end up with an empty diff (Stan Hu)
diff --git a/app/assets/javascripts/awards_handler.coffee b/app/assets/javascripts/awards_handler.coffee
new file mode 100644
index 00000000000..ae42e390c43
--- /dev/null
+++ b/app/assets/javascripts/awards_handler.coffee
@@ -0,0 +1,91 @@
+class @AwardsHandler
+ constructor: (@post_emoji_url, @noteable_type, @noteable_id) ->
+
+ addAward: (emoji) ->
+ @postEmoji emoji, =>
+ @addAwardToEmojiBar(emoji)
+
+ addAwardToEmojiBar: (emoji, custom_path = '') ->
+ if @exist(emoji)
+ if @isActive(emoji)
+ @decrementCounter(emoji)
+ else
+ counter = @findEmojiIcon(emoji).siblings(".counter")
+ counter.text(parseInt(counter.text()) + 1)
+ counter.parent().addClass("active")
+ @addMeToAuthorList(emoji)
+ else
+ @createEmoji(emoji, custom_path)
+
+ exist: (emoji) ->
+ @findEmojiIcon(emoji).length > 0
+
+ isActive: (emoji) ->
+ @findEmojiIcon(emoji).parent().hasClass("active")
+
+ decrementCounter: (emoji) ->
+ counter = @findEmojiIcon(emoji).siblings(".counter")
+
+ if parseInt(counter.text()) > 1
+ counter.text(parseInt(counter.text()) - 1)
+ counter.parent().removeClass("active")
+ @removeMeFromAuthorList(emoji)
+ else
+ award = counter.parent()
+ award.tooltip("destroy")
+ award.remove()
+
+ removeMeFromAuthorList: (emoji) ->
+ award_block = @findEmojiIcon(emoji).parent()
+ authors = award_block.attr("data-original-title").split(", ")
+ authors = _.without(authors, "me").join(", ")
+ award_block.attr("title", authors)
+ @resetTooltip(award_block)
+
+ addMeToAuthorList: (emoji) ->
+ award_block = @findEmojiIcon(emoji).parent()
+ authors = award_block.attr("data-original-title").split(", ")
+ authors.push("me")
+ award_block.attr("title", authors.join(", "))
+ @resetTooltip(award_block)
+
+ resetTooltip: (award) ->
+ award.tooltip("destroy")
+
+ # "destroy" call is asynchronous, this is why we need to set timeout.
+ setTimeout (->
+ award.tooltip()
+ ), 200
+
+
+ createEmoji: (emoji, custom_path) ->
+ nodes = []
+ nodes.push("<div class='award active' title='me'>")
+ nodes.push("<div class='icon' data-emoji='" + emoji + "'>")
+ nodes.push(@getImage(emoji, custom_path))
+ nodes.push("</div>")
+ nodes.push("<div class='counter'>1")
+ nodes.push("</div></div>")
+
+ $(".awards-controls").before(nodes.join("\n"))
+
+ $(".award").tooltip()
+
+ getImage: (emoji, custom_path) ->
+ if custom_path
+ $(".awards-menu li").first().html().replace(/emoji\/.*\.png/, custom_path)
+ else
+ $("li[data-emoji='" + emoji + "']").html()
+
+
+ postEmoji: (emoji, callback) ->
+ $.post @post_emoji_url, { note: {
+ note: emoji
+ noteable_type: @noteable_type
+ noteable_id: @noteable_id
+ }},(data) ->
+ if data.ok
+ callback.call()
+
+ findEmojiIcon: (emoji) ->
+ $(".icon[data-emoji='" + emoji + "']") \ No newline at end of file
diff --git a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
index 5b604adbbb1..195f8b11e5d 100644
--- a/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
+++ b/app/assets/javascripts/blob/blob_file_dropzone.js.coffee
@@ -23,18 +23,6 @@ class @BlobFileDropzone
init: ->
this.on 'addedfile', (file) ->
$('.dropzone-alerts').html('').hide()
- commit_message = form.find('#commit_message')[0]
-
- if /^Upload/.test(commit_message.placeholder)
- commit_message.placeholder = 'Upload ' + file.name
-
- return
-
- this.on 'removedfile', (file) ->
- commit_message = form.find('#commit_message')[0]
-
- if /^Upload/.test(commit_message.placeholder)
- commit_message.placeholder = 'Upload new file'
return
@@ -47,8 +35,9 @@ class @BlobFileDropzone
return
this.on 'sending', (file, xhr, formData) ->
- formData.append('new_branch', form.find('#new_branch').val())
- formData.append('commit_message', form.find('#commit_message').val())
+ formData.append('new_branch', form.find('.js-new-branch').val())
+ formData.append('create_merge_request', form.find('.js-create-merge-request').val())
+ formData.append('commit_message', form.find('.js-commit-message').val())
return
# Override behavior of adding error underneath preview
diff --git a/app/assets/javascripts/copy_to_clipboard.js.coffee b/app/assets/javascripts/copy_to_clipboard.js.coffee
index ec4b80cca6f..9c68c5cc1bc 100644
--- a/app/assets/javascripts/copy_to_clipboard.js.coffee
+++ b/app/assets/javascripts/copy_to_clipboard.js.coffee
@@ -9,13 +9,24 @@ $ ->
clipboard.on 'success', (e) ->
$(e.trigger).
tooltip(trigger: 'manual', placement: 'auto bottom', title: 'Copied!').
- tooltip('show')
+ tooltip('show').
+ one('mouseleave', -> $(this).tooltip('hide'))
# Clear the selection and blur the trigger so it loses its border
e.clearSelection()
$(e.trigger).blur()
- # Manually hide the tooltip after 1 second
- setTimeout(->
- $(e.trigger).tooltip('hide')
- , 1000)
+ # Safari doesn't support `execCommand`, so instead we inform the user to
+ # copy manually.
+ #
+ # See http://clipboardjs.com/#browser-support
+ clipboard.on 'error', (e) ->
+ if /Mac/i.test(navigator.userAgent)
+ title = "Press &#8984;-C to copy"
+ else
+ title = "Press Ctrl-C to copy"
+
+ $(e.trigger).
+ tooltip(trigger: 'manual', placement: 'auto bottom', html: true, title: title).
+ tooltip('show').
+ one('mouseleave', -> $(this).tooltip('hide'))
diff --git a/app/assets/javascripts/new_commit_form.js.coffee b/app/assets/javascripts/new_commit_form.js.coffee
new file mode 100644
index 00000000000..2e561dea3e1
--- /dev/null
+++ b/app/assets/javascripts/new_commit_form.js.coffee
@@ -0,0 +1,21 @@
+class @NewCommitForm
+ constructor: (form) ->
+ @newBranch = form.find('.js-new-branch')
+ @originalBranch = form.find('.js-original-branch')
+ @createMergeRequest = form.find('.js-create-merge-request')
+ @createMergeRequestFormGroup = form.find('.js-create-merge-request-form-group')
+
+ @renderDestination()
+ @newBranch.keyup @renderDestination
+
+ renderDestination: =>
+ different = @newBranch.val() != @originalBranch.val()
+
+ if different
+ @createMergeRequestFormGroup.show()
+ @createMergeRequest.prop('checked', true) unless @wasDifferent
+ else
+ @createMergeRequestFormGroup.hide()
+ @createMergeRequest.prop('checked', false)
+
+ @wasDifferent = different
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index ea75c656bcc..7de7632201d 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -113,13 +113,16 @@ class @Notes
renderNote: (note) ->
# render note if it not present in loaded list
# or skip if rendered
- if @isNewNote(note)
+ if @isNewNote(note) && !note.award
@note_ids.push(note.id)
$('ul.main-notes-list').
append(note.html).
syntaxHighlight()
@initTaskList()
+ if note.award
+ awards_handler.addAwardToEmojiBar(note.note, note.emoji_path)
+
###
Check if note does not exists on page
###
@@ -255,7 +258,6 @@ class @Notes
###
addNote: (xhr, note, status) =>
@renderNote(note)
- @updateVotes()
###
Called in response to the new note form being submitted
@@ -473,9 +475,6 @@ class @Notes
form = $(e.target).closest(".js-discussion-note-form")
@removeDiscussionNoteForm(form)
- updateVotes: ->
- true
-
###
Called after an attachment file has been selected.
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index abc27a19e32..3a08ee70bc7 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -101,3 +101,71 @@
background-color: $background-color;
}
}
+
+.awards {
+ @include clearfix;
+ line-height: 34px;
+ margin: 2px 0;
+
+ .award {
+ @include border-radius(5px);
+
+ border: 1px solid;
+ padding: 0px 10px;
+ float: left;
+ margin: 0 5px;
+ border-color: $border-color;
+ cursor: pointer;
+
+ &.active {
+ border-color: $border-gray-light;
+ background-color: $gray-light;
+
+ .counter {
+ font-weight: bold;
+ }
+ }
+
+ .icon {
+ float: left;
+ margin-right: 10px;
+ }
+
+ .counter {
+ float: left;
+ }
+ }
+
+ .awards-controls {
+ margin-left: 10px;
+ float: left;
+
+ .add-award {
+ font-size: 24px;
+ color: $gl-gray;
+ position: relative;
+ top: 2px;
+
+ &:hover,
+ &:link {
+ text-decoration: none;
+ }
+ }
+
+ .awards-menu {
+ padding: $gl-padding;
+ min-width: 214px;
+
+ > li {
+ margin: 5px;
+ }
+ }
+ }
+
+ .awards-menu{
+ li {
+ float: left;
+ margin: 3px;
+ }
+ }
+}
diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb
index 24dd1b5c93a..a4f6aff49b4 100644
--- a/app/controllers/ci/lints_controller.rb
+++ b/app/controllers/ci/lints_controller.rb
@@ -15,10 +15,10 @@ module Ci
@builds = @config_processor.builds
@status = true
end
- rescue Ci::GitlabCiYamlProcessor::ValidationError => e
+ rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
@error = e.message
@status = false
- rescue Exception
+ rescue
@error = "Undefined error"
@status = false
end
diff --git a/app/controllers/concerns/creates_merge_request_for_commit.rb b/app/controllers/concerns/creates_merge_request_for_commit.rb
new file mode 100644
index 00000000000..c7527822158
--- /dev/null
+++ b/app/controllers/concerns/creates_merge_request_for_commit.rb
@@ -0,0 +1,28 @@
+module CreatesMergeRequestForCommit
+ extend ActiveSupport::Concern
+
+ def new_merge_request_path
+ if @project.forked?
+ target_project = @project.forked_from_project || @project
+ target_branch = target_project.repository.root_ref
+ else
+ target_project = @project
+ target_branch = @ref
+ end
+
+ new_namespace_project_merge_request_path(
+ @project.namespace,
+ @project,
+ merge_request: {
+ source_project_id: @project.id,
+ target_project_id: target_project.id,
+ source_branch: @new_branch,
+ target_branch: target_branch
+ }
+ )
+ end
+
+ def create_merge_request?
+ params[:create_merge_request] && @new_branch != @ref
+ end
+end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 93738aa1ee5..31a33bfd237 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -1,6 +1,7 @@
# Controller for viewing a file's blame
class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
+ include CreatesMergeRequestForCommit
include ActionView::Helpers::SanitizeHelper
# Raised when given an invalid file path
@@ -22,21 +23,9 @@ class Projects::BlobController < Projects::ApplicationController
end
def create
- result = Files::CreateService.new(@project, current_user, @commit_params).execute
-
- if result[:status] == :success
- flash[:notice] = "The changes have been successfully committed"
- respond_to do |format|
- format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) }
- format.json { render json: { message: "success", filePath: namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path)) } }
- end
- else
- flash[:alert] = result[:message]
- respond_to do |format|
- format.html { render :new }
- format.json { render json: { message: "failed", filePath: namespace_project_blob_path(@project.namespace, @project, @id) } }
- end
- end
+ create_commit(Files::CreateService, success_path: after_create_path,
+ failure_view: :new,
+ failure_path: namespace_project_new_blob_path(@project.namespace, @project, @ref))
end
def show
@@ -47,21 +36,9 @@ class Projects::BlobController < Projects::ApplicationController
end
def update
- result = Files::UpdateService.new(@project, current_user, @commit_params).execute
-
- if result[:status] == :success
- flash[:notice] = "Your changes have been successfully committed"
- respond_to do |format|
- format.html { redirect_to after_edit_path }
- format.json { render json: { message: "success", filePath: after_edit_path } }
- end
- else
- flash[:alert] = result[:message]
- respond_to do |format|
- format.html { render :edit }
- format.json { render json: { message: "failed", filePath: namespace_project_new_blob_path(@project.namespace, @project, @id) } }
- end
- end
+ create_commit(Files::UpdateService, success_path: after_edit_path,
+ failure_view: :edit,
+ failure_path: namespace_project_blob_path(@project.namespace, @project, @id))
end
def preview
@@ -77,7 +54,7 @@ class Projects::BlobController < Projects::ApplicationController
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch)
+ redirect_to after_destroy_path
else
flash[:alert] = result[:message]
render :show
@@ -131,15 +108,51 @@ class Projects::BlobController < Projects::ApplicationController
render_404
end
+ def create_commit(service, success_path:, failure_view:, failure_path:)
+ result = service.new(@project, current_user, @commit_params).execute
+
+ if result[:status] == :success
+ flash[:notice] = "Your changes have been successfully committed"
+ respond_to do |format|
+ format.html { redirect_to success_path }
+ format.json { render json: { message: "success", filePath: success_path } }
+ end
+ else
+ flash[:alert] = result[:message]
+ respond_to do |format|
+ format.html { render failure_view }
+ format.json { render json: { message: "failed", filePath: failure_path } }
+ end
+ end
+ end
+
+ def after_create_path
+ @after_create_path ||=
+ if create_merge_request?
+ new_merge_request_path
+ else
+ namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @file_path))
+ end
+ end
+
def after_edit_path
@after_edit_path ||=
- if from_merge_request
+ if create_merge_request?
+ new_merge_request_path
+ elsif from_merge_request && @new_branch == @ref
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
- elsif @target_branch.present?
- namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
else
- namespace_project_blob_path(@project.namespace, @project, @id)
+ namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @path))
+ end
+ end
+
+ def after_destroy_path
+ @after_destroy_path ||=
+ if create_merge_request?
+ new_merge_request_path
+ else
+ namespace_project_tree_path(@project.namespace, @project, @new_branch)
end
end
@@ -154,7 +167,7 @@ class Projects::BlobController < Projects::ApplicationController
def editor_variables
@current_branch = @ref
- @target_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref
+ @new_branch = params[:new_branch].present? ? sanitized_new_branch_name : @ref
@file_path =
if action_name.to_s == 'create'
@@ -174,7 +187,7 @@ class Projects::BlobController < Projects::ApplicationController
@commit_params = {
file_path: @file_path,
current_branch: @current_branch,
- target_branch: @target_branch,
+ target_branch: @new_branch,
commit_message: params[:commit_message],
file_content: params[:content],
file_content_encoding: params[:encoding]
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index e74c2905e48..5250a0f5e67 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -60,7 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController
def show
@participants = @issue.participants(current_user)
@note = @project.notes.new(noteable: @issue)
- @notes = @issue.notes.with_associations.fresh
+ @notes = @issue.notes.nonawards.with_associations.fresh
@noteable = @issue
respond_with(@issue)
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 188f0cc4cea..6378a1f56b0 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -254,7 +254,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
- @notes = @merge_request.mr_and_commit_notes.inc_author.fresh
+ @notes = @merge_request.mr_and_commit_notes.nonawards.inc_author.fresh
@discussions = Note.discussions_from_notes(@notes)
@noteable = @merge_request
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index 41cd08c93c6..263b8b8d94e 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -3,7 +3,7 @@ class Projects::NotesController < Projects::ApplicationController
before_action :authorize_read_note!
before_action :authorize_create_note!, only: [:create]
before_action :authorize_admin_note!, only: [:update, :destroy]
- before_action :find_current_user_notes, except: [:destroy, :delete_attachment]
+ before_action :find_current_user_notes, except: [:destroy, :delete_attachment, :award_toggle]
def index
current_fetched_at = Time.now.to_i
@@ -58,6 +58,27 @@ class Projects::NotesController < Projects::ApplicationController
end
end
+ def award_toggle
+ noteable = note_params[:noteable_type] == "issue" ? Issue : MergeRequest
+ noteable = noteable.find_by!(id: note_params[:noteable_id], project: project)
+
+ data = {
+ author: current_user,
+ is_award: true,
+ note: note_params[:note]
+ }
+
+ note = noteable.notes.find_by(data)
+
+ if note
+ note.destroy
+ else
+ Notes::CreateService.new(project, current_user, note_params).execute
+ end
+
+ render json: { ok: true }
+ end
+
private
def note
@@ -111,6 +132,9 @@ class Projects::NotesController < Projects::ApplicationController
id: note.id,
discussion_id: note.discussion_id,
html: note_to_html(note),
+ award: note.is_award,
+ emoji_path: note.is_award ? ::AwardEmoji.path_to_emoji_image(note.note) : "",
+ note: note.note,
discussion_html: note_to_discussion_html(note),
discussion_with_diff_html: note_to_discussion_with_diff_html(note)
}
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index bdcb1a3e297..8f272ad1281 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -1,6 +1,7 @@
# Controller for viewing a repository's file structure
class Projects::TreeController < Projects::ApplicationController
include ExtractsPath
+ include CreatesMergeRequestForCommit
include ActionView::Helpers::SanitizeHelper
before_action :require_non_empty_project, except: [:new, :create]
@@ -43,7 +44,7 @@ class Projects::TreeController < Projects::ApplicationController
if result && result[:status] == :success
flash[:notice] = "The directory has been successfully created"
respond_to do |format|
- format.html { redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name)) }
+ format.html { redirect_to after_create_dir_path }
end
else
flash[:alert] = message
@@ -53,6 +54,8 @@ class Projects::TreeController < Projects::ApplicationController
end
end
+ private
+
def assign_dir_vars
@new_branch = params[:new_branch].present? ? sanitize(strip_tags(params[:new_branch])) : @ref
@dir_name = File.join(@path, params[:dir_name])
@@ -63,4 +66,12 @@ class Projects::TreeController < Projects::ApplicationController
commit_message: params[:commit_message],
}
end
+
+ def after_create_dir_path
+ if create_merge_request?
+ new_merge_request_path
+ else
+ namespace_project_blob_path(@project.namespace, @project, File.join(@new_branch, @dir_name))
+ end
+ end
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 9f9f9a92f11..08f2483af33 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -1,6 +1,9 @@
class SnippetsController < ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
+ # Allow read snippet
+ before_action :authorize_read_snippet!, only: [:show]
+
# Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update]
@@ -79,10 +82,14 @@ class SnippetsController < ApplicationController
[Snippet::PUBLIC, Snippet::INTERNAL]).
find(params[:id])
else
- PersonalSnippet.are_public.find(params[:id])
+ PersonalSnippet.find(params[:id])
end
end
+ def authorize_read_snippet!
+ authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
+ end
+
def authorize_update_snippet!
return render_404 unless can?(current_user, :update_personal_snippet, @snippet)
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index c407dfc163a..3d5e8b6fbe7 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -62,10 +62,10 @@ class IssuableFinder
if project?
@project = Project.find(params[:project_id])
-
+
unless Ability.abilities.allowed?(current_user, :read_project, @project)
@project = nil
- end
+ end
else
@project = nil
end
@@ -77,11 +77,11 @@ class IssuableFinder
return @projects if defined?(@projects)
if project?
- project
+ @projects = project
elsif current_user && params[:authorized_only].presence && !current_user_related?
- current_user.authorized_projects
+ @projects = current_user.authorized_projects
else
- ProjectsFinder.new.execute(current_user)
+ @projects = ProjectsFinder.new.execute(current_user)
end
end
@@ -190,8 +190,10 @@ class IssuableFinder
def by_project(items)
items =
- if projects
- items.of_projects(projects).references(:project)
+ if project?
+ items.of_projects(projects).references_project
+ elsif projects
+ items.merge(projects.reorder(nil)).join_project
else
items.none
end
@@ -206,7 +208,9 @@ class IssuableFinder
end
def sort(items)
- items.sort(params[:sort])
+ # Ensure we always have an explicit sort order (instead of inheriting
+ # multiple orders when combining ActiveRecord::Relation objects).
+ params[:sort] ? items.sort(params[:sort]) : items.reorder(id: :desc)
end
def by_assignee(items)
diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb
index ab252821b52..fa4c635f55c 100644
--- a/app/finders/notes_finder.rb
+++ b/app/finders/notes_finder.rb
@@ -12,9 +12,9 @@ class NotesFinder
when "commit"
project.notes.for_commit_id(target_id).not_inline
when "issue"
- project.issues.find(target_id).notes.inc_author
+ project.issues.find(target_id).notes.nonawards.inc_author
when "merge_request"
- project.merge_requests.find(target_id).mr_and_commit_notes.inc_author
+ project.merge_requests.find(target_id).mr_and_commit_notes.nonawards.inc_author
when "snippet", "project_snippet"
project.snippets.find(target_id).notes
else
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index beb083d82dc..2c791aa5682 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -87,6 +87,31 @@ module IssuesHelper
merge_requests.map(&:to_reference).to_sentence(last_word_connector: ', or ')
end
+ def url_to_emoji(name)
+ emoji_path = ::AwardEmoji.path_to_emoji_image(name)
+ url_to_image(emoji_path)
+ end
+
+ def emoji_author_list(notes, current_user)
+ list = notes.map do |note|
+ note.author == current_user ? "me" : note.author.username
+ end
+
+ list.join(", ")
+ end
+
+ def emoji_list
+ ::AwardEmoji::EMOJI_LIST
+ end
+
+ def note_active_class(notes, current_user)
+ if current_user && notes.pluck(:author_id).include?(current_user.id)
+ "active"
+ else
+ ""
+ end
+ end
+
# Required for Gitlab::Markdown::IssueReferenceFilter
module_function :url_for_issue
end
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index 728d877ace2..b804d4f4e3b 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -8,14 +8,6 @@ module MergeRequestsHelper
)
end
- def new_mr_path_for_fork_from_push_event(event)
- new_namespace_project_merge_request_path(
- event.project.namespace,
- event.project,
- new_mr_from_push_event(event, event.project.forked_from_project)
- )
- end
-
def new_mr_from_push_event(event, target_project)
{
merge_request: {
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 500af08d209..07f3a56ec7a 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -1,8 +1,8 @@
class Ability
class << self
def allowed(user, subject)
- return not_auth_abilities(user, subject) if user.nil?
- return [] unless user.kind_of?(User)
+ return anonymous_abilities(user, subject) if user.nil?
+ return [] unless user.is_a?(User)
return [] if user.blocked?
case subject.class.name
@@ -20,15 +20,25 @@ class Ability
end.concat(global_abilities(user))
end
- # List of possible abilities
- # for non-authenticated user
- def not_auth_abilities(user, subject)
- project = if subject.kind_of?(Project)
+ # List of possible abilities for anonymous user
+ def anonymous_abilities(user, subject)
+ case true
+ when subject.is_a?(PersonalSnippet)
+ anonymous_personal_snippet_abilities(subject)
+ when subject.is_a?(Project) || subject.respond_to?(:project)
+ anonymous_project_abilities(subject)
+ when subject.is_a?(Group) || subject.respond_to?(:group)
+ anonymous_group_abilities(subject)
+ else
+ []
+ end
+ end
+
+ def anonymous_project_abilities(subject)
+ project = if subject.is_a?(Project)
subject
- elsif subject.respond_to?(:project)
- subject.project
else
- nil
+ subject.project
end
if project && project.public?
@@ -48,19 +58,29 @@ class Ability
rules - project_disabled_features_rules(project)
else
- group = if subject.kind_of?(Group)
- subject
- elsif subject.respond_to?(:group)
- subject.group
- else
- nil
- end
+ []
+ end
+ end
- if group && group.public_profile?
- [:read_group]
- else
- []
- end
+ def anonymous_group_abilities(subject)
+ group = if subject.is_a?(Group)
+ subject
+ else
+ subject.group
+ end
+
+ if group && group.public_profile?
+ [:read_group]
+ else
+ []
+ end
+ end
+
+ def anonymous_personal_snippet_abilities(snippet)
+ if snippet.public?
+ [:read_personal_snippet]
+ else
+ []
end
end
@@ -280,7 +300,7 @@ class Ability
end
end
- [:note, :project_snippet, :personal_snippet].each do |name|
+ [:note, :project_snippet].each do |name|
define_method "#{name}_abilities" do |user, subject|
rules = []
@@ -300,6 +320,24 @@ class Ability
end
end
+ def personal_snippet_abilities(user, snippet)
+ rules = []
+
+ if snippet.author == user
+ rules += [
+ :read_personal_snippet,
+ :update_personal_snippet,
+ :admin_personal_snippet
+ ]
+ end
+
+ if snippet.public? || snippet.internal?
+ rules << :read_personal_snippet
+ end
+
+ rules
+ end
+
def group_member_abilities(user, subject)
rules = []
target_user = subject.user
diff --git a/app/models/ci/commit.rb b/app/models/ci/commit.rb
index 33b57173928..971e899de84 100644
--- a/app/models/ci/commit.rb
+++ b/app/models/ci/commit.rb
@@ -188,13 +188,13 @@ module Ci
end
def config_processor
+ return nil unless ci_yaml_file
@config_processor ||= Ci::GitlabCiYamlProcessor.new(ci_yaml_file, gl_project.path_with_namespace)
- rescue Ci::GitlabCiYamlProcessor::ValidationError => e
+ rescue Ci::GitlabCiYamlProcessor::ValidationError, Psych::SyntaxError => e
save_yaml_error(e.message)
nil
- rescue Exception => e
- logger.error e.message + "\n" + e.backtrace.join("\n")
- save_yaml_error("Undefined yaml error")
+ rescue
+ save_yaml_error("Undefined error")
nil
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 492a026add9..2dafb5e752f 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -35,6 +35,9 @@ module Issuable
scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') }
scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') }
+ scope :join_project, -> { joins(:project) }
+ scope :references_project, -> { references(:project) }
+
delegate :name,
:email,
to: :author,
@@ -89,41 +92,6 @@ module Issuable
opened? || reopened?
end
- #
- # Votes
- #
-
- # Return the number of -1 comments (downvotes)
- def downvotes
- filter_superceded_votes(notes.select(&:downvote?), notes).size
- end
-
- def downvotes_in_percent
- if votes_count.zero?
- 0
- else
- 100.0 - upvotes_in_percent
- end
- end
-
- # Return the number of +1 comments (upvotes)
- def upvotes
- filter_superceded_votes(notes.select(&:upvote?), notes).size
- end
-
- def upvotes_in_percent
- if votes_count.zero?
- 0
- else
- 100.0 / votes_count * upvotes
- end
- end
-
- # Return the total number of votes
- def votes_count
- upvotes + downvotes
- end
-
def subscribed?(user)
subscription = subscriptions.find_by_user_id(user.id)
@@ -183,18 +151,4 @@ module Issuable
def notes_with_associations
notes.includes(:author, :project)
end
-
- private
-
- def filter_superceded_votes(votes, notes)
- filteredvotes = [] + votes
-
- votes.each do |vote|
- if vote.superceded?(notes)
- filteredvotes.delete(vote)
- end
- end
-
- filteredvotes
- end
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 2eb03b8ba5b..1e8d9908f0a 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -134,6 +134,9 @@ class MergeRequest < ActiveRecord::Base
scope :closed, -> { with_state(:closed) }
scope :closed_and_merged, -> { with_states(:closed, :merged) }
+ scope :join_project, -> { joins(:target_project) }
+ scope :references_project, -> { references(:target_project) }
+
def self.reference_prefix
'!'
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 0b3aa30abd7..e30be444eb5 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -40,16 +40,20 @@ class Note < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true
validates :note, :project, presence: true
+ validates :note, uniqueness: { scope: [:author, :noteable_type, :noteable_id] }, if: ->(n) { n.is_award }
validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true
# Attachments are deprecated and are handled by Markdown uploader
validates :attachment, file_size: { maximum: :max_attachment_size }
validates :noteable_id, presence: true, if: ->(n) { n.noteable_type.present? && n.noteable_type != 'Commit' }
validates :commit_id, presence: true, if: ->(n) { n.noteable_type == 'Commit' }
+ validates :author, presence: true
mount_uploader :attachment, AttachmentUploader
# Scopes
+ scope :awards, ->{ where(is_award: true) }
+ scope :nonawards, ->{ where(is_award: false) }
scope :for_commit_id, ->(commit_id) { where(noteable_type: "Commit", commit_id: commit_id) }
scope :inline, ->{ where("line_code IS NOT NULL") }
scope :not_inline, ->{ where(line_code: [nil, '']) }
@@ -97,6 +101,12 @@ class Note < ActiveRecord::Base
def search(query)
where("LOWER(note) like :query", query: "%#{query.downcase}%")
end
+
+ def grouped_awards
+ awards.select(:note).distinct.map do |note|
+ [ note.note, where(note: note.note) ]
+ end
+ end
end
def cross_reference?
@@ -288,44 +298,6 @@ class Note < ActiveRecord::Base
nil
end
- DOWNVOTES = %w(-1 :-1: :thumbsdown: :thumbs_down_sign:)
-
- # Check if the note is a downvote
- def downvote?
- votable? && note.start_with?(*DOWNVOTES)
- end
-
- UPVOTES = %w(+1 :+1: :thumbsup: :thumbs_up_sign:)
-
- # Check if the note is an upvote
- def upvote?
- votable? && note.start_with?(*UPVOTES)
- end
-
- def superceded?(notes)
- return false unless vote?
-
- notes.each do |note|
- next if note == self
-
- if note.vote? &&
- self[:author_id] == note[:author_id] &&
- self[:created_at] <= note[:created_at]
- return true
- end
- end
-
- false
- end
-
- def vote?
- upvote? || downvote?
- end
-
- def votable?
- for_issue? || (for_merge_request? && !for_diff_line?)
- end
-
# Mentionable override.
def gfm_reference(from_project = nil)
noteable.gfm_reference(from_project)
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index ccb6b97858c..f11690aa3f4 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -58,12 +58,6 @@ class GitPushService
@push_data = build_push_data(oldrev, newrev, ref)
- # If CI was disabled but .gitlab-ci.yml file was pushed
- # we enable CI automatically
- if !project.builds_enabled? && gitlab_ci_yaml?(newrev)
- project.enable_ci
- end
-
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks)
@@ -134,10 +128,4 @@ class GitPushService
def commit_user(commit)
commit.author || user
end
-
- def gitlab_ci_yaml?(sha)
- @project.repository.blob_at(sha, '.gitlab-ci.yml')
- rescue Rugged::ReferenceError
- nil
- end
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 2001dc89c33..25a985df4d8 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,11 +5,16 @@ module Notes
note.author = current_user
note.system = false
+ if contains_emoji_only?(params[:note])
+ note.is_award = true
+ note.note = emoji_name(params[:note])
+ end
+
if note.save
notification_service.new_note(note)
- # Skip system notes, like status changes and cross-references.
- unless note.system
+ # Skip system notes, like status changes and cross-references and awards
+ unless note.system || note.is_award
event_service.leave_note(note, note.author)
note.create_cross_references!
execute_hooks(note)
@@ -28,5 +33,13 @@ module Notes
note.project.execute_hooks(note_data, :note_hooks)
note.project.execute_services(note_data, :note_hooks)
end
+
+ def contains_emoji_only?(note)
+ note =~ /\A:?[-_+[:alnum:]]*:?\s?\z/
+ end
+
+ def emoji_name(note)
+ note.match(/\A:?([-_+[:alnum:]]*):?\s?/)[1]
+ end
end
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index bbfe755f44a..d6550fbb555 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -102,6 +102,7 @@ class NotificationService
# ignore gitlab service messages
return true if note.note.start_with?('Status changed to closed')
return true if note.cross_reference? && note.system == true
+ return true if note.is_award
target = note.noteable
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index 63ebfc9381f..7e6301abde8 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -2,9 +2,12 @@
%input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox")
.zen-backdrop
- classes << ' js-gfm-input markdown-area'
- = f.text_area attr, class: classes, placeholder: ''
+ - if defined?(f) && f
+ = f.text_area attr, class: classes, placeholder: ''
+ - else
+ = text_area_tag attr, nil, class: classes, placeholder: ''
%a.zen-enter-link(tabindex="-1" href="#")
- %i.fa.fa-expand
+ = icon('expand')
Edit in fullscreen
%a.zen-leave-link(href="#")
- %i.fa.fa-compress
+ = icon('compress')
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index 373b3a0c5b0..ba3e0c3c590 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -19,4 +19,4 @@
- if allowed_tree_edit?
.btn-group{ role: "group" }
%button.btn.btn-default{ 'data-target' => '#modal-upload-blob', 'data-toggle' => 'modal' } Replace
- %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Remove
+ %button.btn.btn-remove{ 'data-target' => '#modal-remove-blob', 'data-toggle' => 'modal' } Delete
diff --git a/app/views/projects/blob/_new_dir.html.haml b/app/views/projects/blob/_new_dir.html.haml
index a0fc8bbd752..13b5ffd17ff 100644
--- a/app/views/projects/blob/_new_dir.html.haml
+++ b/app/views/projects/blob/_new_dir.html.haml
@@ -5,21 +5,19 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title Create New Directory
.modal-body
- = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, id: 'dir-create-form', class: 'form-horizontal' do
+ = form_tag namespace_project_create_dir_path(@project.namespace, @project, @id), method: :post, remote: false, class: 'form-horizontal js-create-dir-form' do
.form-group
= label_tag :dir_name, 'Directory Name', class: 'control-label'
.col-sm-10
= text_field_tag :dir_name, params[:dir_name], placeholder: "Directory name", required: true, class: 'form-control'
- = render 'shared/commit_message_container', params: params, placeholder: ''
- - unless @project.empty_repo?
- .form-group
- = label_tag :branch_name, 'Branch', class: 'control-label'
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
+
+ = render 'shared/new_commit_form', placeholder: "Add new directory"
+
.form-group
.col-sm-offset-2.col-sm-10
= submit_tag "Create directory", class: 'btn btn-primary btn-create'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
- disableButtonIfAnyEmptyField($("#dir-create-form"), ".form-control", ".btn-create");
+ disableButtonIfAnyEmptyField($(".js-create-dir-form"), ".form-control", ".btn-create");
+ new NewCommitForm($('.js-create-dir-form'))
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index cae5ff01099..1cf19a7d3db 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -3,16 +3,16 @@
.modal-content
.modal-header
%a.close{href: "#", "data-dismiss" => "modal"} ×
- %h3.page-title Remove #{@blob.name}
- %p.light
- From branch
- %strong= @ref
+ %h3.page-title Delete #{@blob.name}
.modal-body
- = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-requires-input' do
- = render 'shared/commit_message_container', params: params,
- placeholder: 'Removed this file because...'
+ = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-replace-blob-form js-requires-input' do
+ = render 'shared/new_commit_form', placeholder: "Delete #{@blob.name}"
+
.form-group
.col-sm-offset-2.col-sm-10
- = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
+ = button_tag 'Delete file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
+
+:javascript
+ new NewCommitForm($('.js-replace-blob-form'))
diff --git a/app/views/projects/blob/_upload.html.haml b/app/views/projects/blob/_upload.html.haml
index a1c54e731f0..3bb61f0c944 100644
--- a/app/views/projects/blob/_upload.html.haml
+++ b/app/views/projects/blob/_upload.html.haml
@@ -5,7 +5,7 @@
%a.close{href: "#", "data-dismiss" => "modal"} ×
%h3.page-title #{title}
.modal-body
- = form_tag form_path, method: method, class: 'blob-file-upload-form-js form-horizontal' do
+ = form_tag form_path, method: method, class: 'js-upload-blob-form form-horizontal' do
.dropzone
.dropzone-previews.blob-upload-dropzone-previews
%p.dz-message.light
@@ -13,19 +13,15 @@
= link_to 'click to upload', '#', class: "markdown-selector"
%br
.dropzone-alerts{class: "alert alert-danger data", style: "display:none"}
- = render 'shared/commit_message_container', params: params,
- placeholder: placeholder
- - unless @project.empty_repo?
- .form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
+
+ = render 'shared/new_commit_form', placeholder: placeholder
+
.form-group
.col-sm-offset-2.col-sm-10
= button_tag button_title, class: 'btn btn-small btn-primary btn-upload-file', id: 'submit-all'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
- disableButtonIfEmptyField($('.blob-file-upload-form-js').find('#commit_message'), '.btn-upload-file');
- new BlobFileDropzone($('.blob-file-upload-form-js'), '#{method}');
+ disableButtonIfEmptyField($('.js-upload-blob-form').find('.js-commit-message'), '.btn-upload-file');
+ new BlobFileDropzone($('.js-upload-blob-form'), '#{method}');
+ new NewCommitForm($('.js-upload-blob-form'))
diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml
index a811adc5094..56745165251 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -13,15 +13,9 @@
%i.fa.fa-eye
= editing_preview_title(@blob.name)
- = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input') do
+ = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input js-edit-blob-form') do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
- = render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}"
-
- .form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
+ = render 'shared/new_commit_form', placeholder: "Update #{@blob.name}"
= hidden_field_tag 'last_commit', @last_commit
= hidden_field_tag 'content', '', id: "file-content"
@@ -30,3 +24,4 @@
:javascript
blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}")
+ new NewCommitForm($('.js-edit-blob-form'))
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 7975137c37f..1ff68005450 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -2,20 +2,13 @@
= render "header_title"
.gray-content-block.top-block
- Create a new file
+ %h3.page-title
+ Create New File
.file-editor
- = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do
+ = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal js-new-blob-form js-requires-input') do
= render 'projects/blob/editor', ref: @ref
- = render 'shared/commit_message_container', params: params,
- placeholder: 'Add new file'
-
- - unless @project.empty_repo?
- .form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control js-quick-submit"
+ = render 'shared/new_commit_form', placeholder: "Add new file"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
@@ -23,3 +16,4 @@
:javascript
blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null)
+ new NewCommitForm($('.js-new-blob-form'))
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index f52b89f6921..b7276868ce6 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -10,6 +10,4 @@
= render 'projects/blob/remove'
- title = "Replace #{@blob.name}"
- = render 'projects/blob/upload', title: title, placeholder: title,
- button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id),
- method: :put
+ = render 'projects/blob/upload', title: title, placeholder: title, button_title: 'Replace file', form_path: namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index c5fd863ae99..020952dd001 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -7,7 +7,7 @@
= render 'shared/show_aside'
-.gray-content-block.second-block
+.gray-content-block.second-block.oneline-block
.row
.col-md-9
.votes-holder.pull-right
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 55ce912829d..d7657ee7e40 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -29,8 +29,6 @@
.issue-info
= "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
- - if issue.votes_count > 0
- = render 'votes/votes_inline', votable: issue
- if issue.milestone
&nbsp;
%span
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 7e60782ff5b..cb75bd8c5ba 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -14,8 +14,10 @@
#votes= render 'votes/votes_block', votable: @merge_request
= render "projects/merge_requests/show/participants"
.col-md-3
- %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
- = cross_project_reference(@project, @merge_request)
+ .input-group.cross-project-reference
+ %span.slead.has_tooltip{title: 'Cross-project reference'}
+ = cross_project_reference(@project, @merge_request)
+ = clipboard_button
.row
%section.col-md-9
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index c5234c0618c..83e8ad11989 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -34,8 +34,6 @@
.merge-request-info
= "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe
- - if merge_request.votes_count > 0
- = render 'votes/votes_inline', votable: merge_request
- if merge_request.milestone_id?
&nbsp;
%span
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index efa7dd01cc2..dd0abc8c746 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -35,26 +35,6 @@
- if note.updated_by && note.updated_by != note.author
by #{link_to_member(note.project, note.updated_by, avatar: false, author_class: nil)}
- - if note.superceded?(@notes)
- - if note.upvote?
- %span.vote.upvote.label.label-gray.strikethrough
- = icon('thumbs-up')
- \+1
- - if note.downvote?
- %span.vote.downvote.label.label-gray.strikethrough
- = icon('thumbs-down')
- \-1
- - else
- - if note.upvote?
- %span.vote.upvote.label.label-success
- = icon('thumbs-up')
- \+1
- - if note.downvote?
- %span.vote.downvote.label.label-danger
- = icon('thumbs-down')
- \-1
-
-
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
.note-text
= preserve do
diff --git a/app/views/projects/releases/edit.html.haml b/app/views/projects/releases/edit.html.haml
index e7db09cdaa9..f516b65ecd0 100644
--- a/app/views/projects/releases/edit.html.haml
+++ b/app/views/projects/releases/edit.html.haml
@@ -11,10 +11,9 @@
.prepend-top-default
= form_for(@release, method: :put, url: namespace_project_tag_release_path(@project.namespace, @project, @tag.name), html: { class: 'form-horizontal gfm-form release-form' }) do |f|
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit'
+ = render 'projects/zen', f: f, attr: :description, classes: 'description js-quick-submit form-control'
= render 'projects/notes/hints'
.error-alert
.prepend-top-default
= f.submit 'Save changes', class: 'btn btn-save'
= link_to "Cancel", namespace_project_tag_path(@project.namespace, @project, @tag.name), class: "btn btn-default btn-cancel"
-
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index e106be794f1..86aa15dc5b3 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -10,7 +10,7 @@
New git tag
%hr
-= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal tag-form" do
+= form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal gfm-form tag-form" do
.form-group
= label_tag :tag_name, 'Name for new tag', class: 'control-label'
.col-sm-10
@@ -30,16 +30,7 @@
= label_tag :release_description, 'Release notes', class: 'control-label'
.col-sm-10
= render layout: 'projects/md_preview', locals: { preview_class: "md-preview", referenced_users: true } do
- .zennable
- %input#zen-toggle-comment.zen-toggle-comment(tabindex="-1" type="checkbox")
- .zen-backdrop
- = text_area_tag :release_description, nil, class: 'js-gfm-input markdown-area description js-quick-submit form-control', placeholder: ''
- %a.zen-enter-link(tabindex="-1" href="#")
- = icon('expand')
- Edit in fullscreen
- %a.zen-leave-link(href="#")
- = icon('compress')
-
+ = render 'projects/zen', attr: :release_description, classes: 'description js-quick-submit form-control'
= render 'projects/notes/hints'
.help-block (Optional) You can add release notes to your tag. It will be stored in the GitLab database and shown on the tags page
.form-actions
diff --git a/app/views/projects/tree/_tree_content.html.haml b/app/views/projects/tree/_tree_content.html.haml
index ee4c9d1693d..c64e684df26 100644
--- a/app/views/projects/tree/_tree_content.html.haml
+++ b/app/views/projects/tree/_tree_content.html.haml
@@ -30,7 +30,7 @@
= render "projects/tree/readme", readme: tree.readme
- if allowed_tree_edit?
- = render 'projects/blob/upload', title: 'Upload', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
+ = render 'projects/blob/upload', title: 'Upload New File', placeholder: 'Upload new file', button_title: 'Upload file', form_path: namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post
= render 'projects/blob/new_dir'
:javascript
diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml
index cc3f1268f8b..7c57924277e 100644
--- a/app/views/shared/_commit_message_container.html.haml
+++ b/app/views/shared/_commit_message_container.html.haml
@@ -1,13 +1,15 @@
.form-group.commit_message-group
- = label_tag 'commit_message', class: 'control-label' do
+ - nonce = SecureRandom.hex
+ = label_tag "commit_message-#{nonce}", class: 'control-label' do
Commit message
.col-sm-10
.commit-message-container
.max-width-marker
= text_area_tag 'commit_message',
(params[:commit_message] || local_assigns[:text]),
- class: 'form-control js-quick-submit', placeholder: local_assigns[:placeholder],
- required: true, rows: (local_assigns[:rows] || 3)
+ class: 'form-control js-commit-message js-quick-submit', placeholder: local_assigns[:placeholder],
+ required: true, rows: (local_assigns[:rows] || 3),
+ id: "commit_message-#{nonce}"
- if local_assigns[:hint]
%p.hint
Try to keep the first line under 52 characters
diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml
index 5f51b0d450f..2a44817e05a 100644
--- a/app/views/shared/_confirm_modal.html.haml
+++ b/app/views/shared/_confirm_modal.html.haml
@@ -14,7 +14,7 @@
%br
Please type
%code.js-confirm-danger-match #{phrase}
- to proceed or close this modal to cancel
+ to proceed or close this modal to cancel.
.form-group
= text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input'
diff --git a/app/views/shared/_new_commit_form.html.haml b/app/views/shared/_new_commit_form.html.haml
new file mode 100644
index 00000000000..8636341c60d
--- /dev/null
+++ b/app/views/shared/_new_commit_form.html.haml
@@ -0,0 +1,18 @@
+= render 'shared/commit_message_container', placeholder: placeholder
+
+- unless @project.empty_repo?
+ .form-group.branch
+ = label_tag 'branch', class: 'control-label' do
+ Branch
+ .col-sm-10
+ = text_field_tag 'new_branch', @new_branch || @ref, class: "form-control js-new-branch"
+
+ .form-group.js-create-merge-request-form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ - nonce = SecureRandom.hex
+ = label_tag "create_merge_request-#{nonce}" do
+ = check_box_tag 'create_merge_request', 1, true, class: 'js-create-merge-request', id: "create_merge_request-#{nonce}"
+ Start a <strong>new merge request</strong> with this commit
+
+ = hidden_field_tag 'original_branch', @ref, class: 'js-original-branch'
diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml
index 36ea6742064..7eb27c12d33 100644
--- a/app/views/votes/_votes_block.html.haml
+++ b/app/views/votes/_votes_block.html.haml
@@ -1,10 +1,32 @@
-.votes.votes-block
- .btn-group
- - unless votable.upvotes.zero?
- .btn.btn-sm.disabled.cgreen
- %i.fa.fa-thumbs-up
- = votable.upvotes
- - unless votable.downvotes.zero?
- .btn.btn-sm.disabled.cred
- %i.fa.fa-thumbs-down
- = votable.downvotes
+.awards.votes-block
+ - votable.notes.awards.grouped_awards.each do |emoji, notes|
+ .award{class: (note_active_class(notes, current_user)), title: emoji_author_list(notes, current_user)}
+ .icon{"data-emoji" => "#{emoji}"}
+ = image_tag url_to_emoji(emoji), height: "20px", width: "20px"
+ .counter
+ = notes.count
+
+ - if current_user
+ .dropdown.awards-controls
+ %a.add-award{"data-toggle" => "dropdown", "data-target" => "#", "href" => "#"}
+ = icon('smile-o')
+ %ul.dropdown-menu.awards-menu
+ - emoji_list.each do |emoji|
+ %li{"data-emoji" => "#{emoji}"}= image_tag url_to_emoji(emoji), height: "20px", width: "20px"
+
+- if current_user
+ :coffeescript
+ post_emoji_url = "#{award_toggle_namespace_project_notes_path(@project.namespace, @project)}"
+ noteable_type = "#{votable.class.name.underscore}"
+ noteable_id = "#{votable.id}"
+ window.awards_handler = new AwardsHandler(post_emoji_url, noteable_type, noteable_id)
+
+ $(".awards-menu li").click (e)->
+ emoji = $(this).data("emoji")
+ awards_handler.addAward(emoji)
+
+ $(".awards").on "click", ".award", (e)->
+ emoji = $(this).find(".icon").data("emoji")
+ awards_handler.addAward(emoji)
+
+ $(".award").tooltip()
diff --git a/app/views/votes/_votes_inline.html.haml b/app/views/votes/_votes_inline.html.haml
deleted file mode 100644
index 2cb3ae04e1a..00000000000
--- a/app/views/votes/_votes_inline.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-.votes.votes-inline
- - unless votable.upvotes.zero?
- %span.upvotes.cgreen
- + #{votable.upvotes}
- - unless votable.downvotes.zero?
- \/
- - unless votable.downvotes.zero?
- %span.downvotes.cred
- \- #{votable.downvotes}
diff --git a/config/routes.rb b/config/routes.rb
index 0bc2c173453..ac81a2aac76 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -664,6 +664,10 @@ Gitlab::Application.routes.draw do
member do
delete :delete_attachment
end
+
+ collection do
+ post :award_toggle
+ end
end
resources :uploads, only: [:create] do
diff --git a/db/migrate/20151106000015_add_is_award_to_notes.rb b/db/migrate/20151106000015_add_is_award_to_notes.rb
new file mode 100644
index 00000000000..02b271637e9
--- /dev/null
+++ b/db/migrate/20151106000015_add_is_award_to_notes.rb
@@ -0,0 +1,6 @@
+class AddIsAwardToNotes < ActiveRecord::Migration
+ def change
+ add_column :notes, :is_award, :boolean, default: false, null: false
+ add_index :notes, :is_award
+ end
+end
diff --git a/db/migrate/20151109134526_add_issues_state_index.rb b/db/migrate/20151109134526_add_issues_state_index.rb
new file mode 100644
index 00000000000..1c4d2e30171
--- /dev/null
+++ b/db/migrate/20151109134526_add_issues_state_index.rb
@@ -0,0 +1,5 @@
+class AddIssuesStateIndex < ActiveRecord::Migration
+ def change
+ add_index :issues, :state
+ end
+end
diff --git a/db/migrate/20151109134916_add_projects_visibility_level_index.rb b/db/migrate/20151109134916_add_projects_visibility_level_index.rb
new file mode 100644
index 00000000000..600b4bafd98
--- /dev/null
+++ b/db/migrate/20151109134916_add_projects_visibility_level_index.rb
@@ -0,0 +1,5 @@
+class AddProjectsVisibilityLevelIndex < ActiveRecord::Migration
+ def change
+ add_index :projects, :visibility_level
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 462d5ed3b29..5bbe0c908ef 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -384,6 +384,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree
add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree
add_index "issues", ["project_id"], name: "index_issues_on_project_id", using: :btree
+ add_index "issues", ["state"], name: "index_issues_on_state", using: :btree
add_index "issues", ["title"], name: "index_issues_on_title", using: :btree
create_table "keys", force: true do |t|
@@ -555,12 +556,14 @@ ActiveRecord::Schema.define(version: 20151118162244) do
t.boolean "system", default: false, null: false
t.text "st_diff"
t.integer "updated_by_id"
+ t.boolean "is_award", default: false, null: false
end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree
add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree
add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree
+ add_index "notes", ["is_award"], name: "index_notes_on_is_award", using: :btree
add_index "notes", ["line_code"], name: "index_notes_on_line_code", 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
@@ -641,9 +644,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
t.integer "star_count", default: 0, null: false
t.string "import_type"
t.string "import_source"
- t.integer "commit_count", default: 0
- t.boolean "merge_requests_ff_only_enabled", default: false
- t.text "issues_template"
+ t.integer "commit_count", default: 0
t.text "import_error"
end
@@ -653,6 +654,7 @@ ActiveRecord::Schema.define(version: 20151118162244) do
add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree
add_index "projects", ["path"], name: "index_projects_on_path", using: :btree
add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree
+ add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
create_table "protected_branches", force: true do |t|
t.integer "project_id", null: false
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index ffa7f2cdf14..2f17d4ae06b 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -31,8 +31,6 @@ Parameters:
"project_id": 3,
"title": "test1",
"state": "opened",
- "upvotes": 0,
- "downvotes": 0,
"author": {
"id": 1,
"username": "admin",
@@ -77,8 +75,6 @@ Parameters:
"project_id": 3,
"title": "test1",
"state": "merged",
- "upvotes": 0,
- "downvotes": 0,
"author": {
"id": 1,
"username": "admin",
@@ -126,8 +122,6 @@ Parameters:
"updated_at": "2015-02-02T20:08:49.959Z",
"target_branch": "secret_token",
"source_branch": "version-1-9",
- "upvotes": 0,
- "downvotes": 0,
"author": {
"name": "Chad Hamill",
"username": "jarrett",
@@ -198,8 +192,6 @@ Parameters:
"project_id": 3,
"title": "test1",
"state": "opened",
- "upvotes": 0,
- "downvotes": 0,
"author": {
"id": 1,
"username": "admin",
@@ -250,8 +242,6 @@ Parameters:
"title": "test1",
"description": "description1",
"state": "opened",
- "upvotes": 0,
- "downvotes": 0,
"author": {
"id": 1,
"username": "admin",
@@ -304,8 +294,6 @@ Parameters:
"project_id": 3,
"title": "test1",
"state": "merged",
- "upvotes": 0,
- "downvotes": 0,
"author": {
"id": 1,
"username": "admin",
diff --git a/doc/api/notes.md b/doc/api/notes.md
index c683cb883d4..bcba1f62151 100644
--- a/doc/api/notes.md
+++ b/doc/api/notes.md
@@ -32,9 +32,7 @@ Parameters:
"created_at": "2013-09-30T13:46:01Z"
},
"created_at": "2013-10-02T09:22:45Z",
- "system": true,
- "upvote": false,
- "downvote": false
+ "system": true
},
{
"id": 305,
@@ -49,9 +47,7 @@ Parameters:
"created_at": "2013-09-30T13:46:01Z"
},
"created_at": "2013-10-02T09:56:03Z",
- "system": false,
- "upvote": false,
- "downvote": false
+ "system": false
}
]
```
diff --git a/features/project/issues/award_emoji.feature b/features/project/issues/award_emoji.feature
new file mode 100644
index 00000000000..a9bc8ffb9bb
--- /dev/null
+++ b/features/project/issues/award_emoji.feature
@@ -0,0 +1,14 @@
+Feature: Award Emoji
+ Background:
+ Given I sign in as a user
+ And I own project "Shop"
+ And project "Shop" has issue "Bugfix"
+ And I visit "Bugfix" issue page
+
+ @javascript
+ Scenario: I add and remove award in the issue
+ Given I click to emoji-picker
+ And I click to emoji in the picker
+ Then I have award added
+ And I can remove it by clicking to icon
+ \ No newline at end of file
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 69aa79f2d24..e545ea63ca8 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -42,7 +42,7 @@ Feature: Project Source Browse Files
And I fill the new branch name
And I click on "Upload file"
Then I can see the new text file
- And I am redirected to the uploaded file on new branch
+ And I am redirected to the new merge request page
And I can see the new commit message
@javascript
@@ -64,7 +64,7 @@ Feature: Project Source Browse Files
And I fill the commit message
And I fill the new branch name
And I click on "Commit Changes"
- Then I am redirected to the new file on new branch
+ Then I am redirected to the new merge request page
And I should see its new content
@javascript
@@ -134,7 +134,7 @@ Feature: Project Source Browse Files
And I fill the commit message
And I fill the new branch name
And I click on "Commit Changes"
- Then I am redirected to the ".gitignore" on new branch
+ Then I am redirected to the new merge request page
And I should see its new content
@javascript @wip
@@ -154,7 +154,7 @@ Feature: Project Source Browse Files
And I fill the commit message
And I fill the new branch name
And I click on "Create directory"
- Then I am redirected to the new directory
+ Then I am redirected to the new merge request page
@javascript
Scenario: I attempt to create an existing directory
@@ -174,12 +174,12 @@ Feature: Project Source Browse Files
Then I see diff
@javascript
- Scenario: I can remove file and commit
+ Scenario: I can delete file and commit
Given I click on ".gitignore" file in repo
And I see the ".gitignore"
- And I click on "Remove"
+ And I click on "Delete"
And I fill the commit message
- And I click on "Remove file"
+ And I click on "Delete file"
Then I am redirected to the files URL
And I don't see the ".gitignore"
diff --git a/features/steps/project/issues/award_emoji.rb b/features/steps/project/issues/award_emoji.rb
new file mode 100644
index 00000000000..8f7a45dec0e
--- /dev/null
+++ b/features/steps/project/issues/award_emoji.rb
@@ -0,0 +1,41 @@
+class Spinach::Features::AwardEmoji < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedProject
+ include SharedPaths
+ include Select2Helper
+
+ step 'I visit "Bugfix" issue page' do
+ visit namespace_project_issue_path(@project.namespace, @project, @issue)
+ end
+
+ step 'I click to emoji-picker' do
+ page.within ".awards-controls" do
+ page.find(".add-award").click
+ end
+ end
+
+ step 'I click to emoji in the picker' do
+ page.within ".awards-menu" do
+ page.first("img").click
+ end
+ end
+
+ step 'I can remove it by clicking to icon' do
+ page.within ".awards" do
+ page.first(".award").click
+ expect(page).to_not have_selector ".award"
+ end
+ end
+
+ step 'I have award added' do
+ page.within ".awards" do
+ expect(page).to have_selector ".award"
+ expect(page.find(".award .counter")).to have_content "1"
+ end
+ end
+
+ step 'project "Shop" has issue "Bugfix"' do
+ @project = Project.find_by(name: "Shop")
+ @issue = create(:issue, title: "Bugfix", project: project)
+ end
+end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 84725b9b585..f40e0f0d528 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -98,12 +98,12 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
click_button 'Create directory'
end
- step 'I click on "Remove"' do
- click_button 'Remove'
+ step 'I click on "Delete"' do
+ click_button 'Delete'
end
- step 'I click on "Remove file"' do
- click_button 'Remove file'
+ step 'I click on "Delete file"' do
+ click_button 'Delete file'
end
step 'I click on "Replace"' do
@@ -142,7 +142,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I can see new file page' do
- expect(page).to have_content "new file"
+ expect(page).to have_content "Create New File"
expect(page).to have_content "Commit message"
end
@@ -225,10 +225,6 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore'))
end
- step 'I am redirected to the ".gitignore" on new branch' do
- expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/.gitignore'))
- end
-
step 'I am redirected to the permalink URL' do
expect(current_path).to(
eq(namespace_project_blob_path(@project.namespace, @project,
@@ -247,20 +243,8 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
@project.namespace, @project, 'master/' + new_file_name_with_directory))
end
- step 'I am redirected to the new file on new branch' do
- expect(current_path).to eq(namespace_project_blob_path(
- @project.namespace, @project, 'new_branch_name/' + new_file_name))
- end
-
- step 'I am redirected to the uploaded file on new branch' do
- expect(current_path).to eq(namespace_project_blob_path(
- @project.namespace, @project,
- 'new_branch_name/' + File.basename(test_text_file)))
- end
-
- step 'I am redirected to the new directory' do
- expect(current_path).to eq(namespace_project_tree_path(
- @project.namespace, @project, 'new_branch_name/' + new_dir_name))
+ step 'I am redirected to the new merge request page' do
+ expect(current_path).to eq(new_namespace_project_merge_request_path(@project.namespace, @project))
end
step 'I am redirected to the root directory' do
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index d6aec03d7f5..3da6bc415d6 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -162,7 +162,7 @@ module API
end
class MergeRequest < ProjectEntity
- expose :target_branch, :source_branch, :upvotes, :downvotes
+ expose :target_branch, :source_branch
expose :author, :assignee, using: Entities::UserBasic
expose :source_project_id, :target_project_id
expose :label_names, as: :labels
@@ -192,8 +192,6 @@ module API
expose :author, using: Entities::UserBasic
expose :created_at
expose :system?, as: :system
- expose :upvote?, as: :upvote
- expose :downvote?, as: :downvote
end
class MRNote < Grape::Entity
diff --git a/lib/award_emoji.rb b/lib/award_emoji.rb
new file mode 100644
index 00000000000..d58a196c4ef
--- /dev/null
+++ b/lib/award_emoji.rb
@@ -0,0 +1,12 @@
+class AwardEmoji
+ EMOJI_LIST = [
+ "+1", "-1", "100", "blush", "heart", "smile", "rage",
+ "beers", "disappointed", "ok_hand",
+ "helicopter", "shit", "airplane", "alarm_clock",
+ "ambulance", "anguished", "two_hearts", "wink"
+ ]
+
+ def self.path_to_emoji_image(name)
+ "emoji/#{Emoji.emoji_filename(name)}.png"
+ end
+end
diff --git a/spec/benchmarks/finders/issues_finder_spec.rb b/spec/benchmarks/finders/issues_finder_spec.rb
new file mode 100644
index 00000000000..b57a33004a4
--- /dev/null
+++ b/spec/benchmarks/finders/issues_finder_spec.rb
@@ -0,0 +1,55 @@
+require 'spec_helper'
+
+describe IssuesFinder, benchmark: true do
+ describe '#execute' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ let(:label1) { create(:label, project: project, title: 'A') }
+ let(:label2) { create(:label, project: project, title: 'B') }
+
+ before do
+ 10.times do |n|
+ issue = create(:issue, author: user, project: project)
+
+ if n > 4
+ create(:label_link, label: label1, target: issue)
+ create(:label_link, label: label2, target: issue)
+ end
+ end
+ end
+
+ describe 'retrieving issues without labels' do
+ let(:finder) do
+ IssuesFinder.new(user, scope: 'all', label_name: Label::None.title,
+ state: 'opened')
+ end
+
+ benchmark_subject { finder.execute }
+
+ it { is_expected.to iterate_per_second(2000) }
+ end
+
+ describe 'retrieving issues with labels' do
+ let(:finder) do
+ IssuesFinder.new(user, scope: 'all', label_name: label1.title,
+ state: 'opened')
+ end
+
+ benchmark_subject { finder.execute }
+
+ it { is_expected.to iterate_per_second(1000) }
+ end
+
+ describe 'retrieving issues for a single project' do
+ let(:finder) do
+ IssuesFinder.new(user, scope: 'all', label_name: Label::None.title,
+ state: 'opened', project_id: project.id)
+ end
+
+ benchmark_subject { finder.execute }
+
+ it { is_expected.to iterate_per_second(2000) }
+ end
+ end
+end
diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb
new file mode 100644
index 00000000000..e9b823c523c
--- /dev/null
+++ b/spec/controllers/snippets_controller_spec.rb
@@ -0,0 +1,118 @@
+require 'spec_helper'
+
+describe SnippetsController do
+ describe 'GET #show' do
+ let(:user) { create(:user) }
+
+ context 'when the personal snippet is private' do
+ let(:personal_snippet) { create(:personal_snippet, :private, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ context 'when signed in user is not the author' do
+ let(:other_author) { create(:author) }
+ let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) }
+
+ it 'responds with status 404' do
+ get :show, id: other_personal_snippet.to_param
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when signed in user is the author' do
+ it 'renders the snippet' do
+ get :show, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to the sign in page' do
+ get :show, id: personal_snippet.to_param
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context 'when the personal snippet is internal' do
+ let(:personal_snippet) { create(:personal_snippet, :internal, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the snippet' do
+ get :show, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'redirects to the sign in page' do
+ get :show, id: personal_snippet.to_param
+
+ expect(response).to redirect_to(new_user_session_path)
+ end
+ end
+ end
+
+ context 'when the personal snippet is public' do
+ let(:personal_snippet) { create(:personal_snippet, :public, author: user) }
+
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'renders the snippet' do
+ get :show, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'renders the snippet' do
+ get :show, id: personal_snippet.to_param
+
+ expect(assigns(:snippet)).to eq(personal_snippet)
+ expect(response.status).to eq(200)
+ end
+ end
+ end
+
+ context 'when the personal snippet does not exist' do
+ context 'when signed in' do
+ before do
+ sign_in(user)
+ end
+
+ it 'responds with status 404' do
+ get :show, id: 'doesntexist'
+
+ expect(response.status).to eq(404)
+ end
+ end
+
+ context 'when not signed in' do
+ it 'responds with status 404' do
+ get :show, id: 'doesntexist'
+
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/factories.rb b/spec/factories.rb
index 200f18f660d..4bf93adabe2 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -165,6 +165,18 @@ FactoryGirl.define do
title
content
file_name
+
+ trait :public do
+ visibility_level Gitlab::VisibilityLevel::PUBLIC
+ end
+
+ trait :internal do
+ visibility_level Gitlab::VisibilityLevel::INTERNAL
+ end
+
+ trait :private do
+ visibility_level Gitlab::VisibilityLevel::PRIVATE
+ end
end
factory :snippet do
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 78a6b631eb2..1f2c4ee77b5 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -127,4 +127,30 @@ describe IssuesHelper do
it { is_expected.to eq("!1, !2, or !3") }
end
+ describe "#url_to_emoji" do
+ it "returns url" do
+ expect(url_to_emoji("smile")).to include("emoji/1F604.png")
+ end
+ end
+
+ describe "#emoji_list" do
+ it "returns url" do
+ expect(emoji_list).to be_kind_of(Array)
+ end
+ end
+
+ describe "#note_active_class" do
+ before do
+ @note = create :note
+ @note1 = create :note
+ end
+
+ it "returns empty string for unauthenticated user" do
+ expect(note_active_class(Note.all, nil)).to eq("")
+ end
+
+ it "returns active string for author" do
+ expect(note_active_class(Note.all, @note.author)).to eq("active")
+ end
+ end
end
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index 7d90f9877c6..6f287719ba6 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -425,8 +425,12 @@ module Ci
end
describe "Error handling" do
+ it "fails to parse YAML" do
+ expect{GitlabCiYamlProcessor.new("invalid: yaml: test")}.to raise_error(Psych::SyntaxError)
+ end
+
it "indicates that object is invalid" do
- expect{GitlabCiYamlProcessor.new("invalid_yaml\n!ccdvlf%612334@@@@")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
+ expect{GitlabCiYamlProcessor.new("invalid_yaml")}.to raise_error(GitlabCiYamlProcessor::ValidationError)
end
it "returns errors if tags parameter is invalid" do
diff --git a/spec/lib/votes_spec.rb b/spec/lib/votes_spec.rb
deleted file mode 100644
index 39e5d054e62..00000000000
--- a/spec/lib/votes_spec.rb
+++ /dev/null
@@ -1,188 +0,0 @@
-require 'spec_helper'
-
-describe Issue, 'Votes' do
- let(:issue) { create(:issue) }
-
- describe "#upvotes" do
- it "with no notes has a 0/0 score" do
- expect(issue.upvotes).to eq(0)
- end
-
- it "should recognize non-+1 notes" do
- add_note "No +1 here"
- expect(issue.notes.size).to eq(1)
- expect(issue.notes.first.upvote?).to be_falsey
- expect(issue.upvotes).to eq(0)
- end
-
- it "should recognize a single +1 note" do
- add_note "+1 This is awesome"
- expect(issue.upvotes).to eq(1)
- end
-
- it 'should recognize multiple +1 notes' do
- add_note '+1 This is awesome', create(:user)
- add_note '+1 I want this', create(:user)
- expect(issue.upvotes).to eq(2)
- end
-
- it 'should not count 2 +1 votes from the same user' do
- add_note '+1 This is awesome'
- add_note '+1 I want this'
- expect(issue.upvotes).to eq(1)
- end
- end
-
- describe "#downvotes" do
- it "with no notes has a 0/0 score" do
- expect(issue.downvotes).to eq(0)
- end
-
- it "should recognize non--1 notes" do
- add_note "Almost got a -1"
- expect(issue.notes.size).to eq(1)
- expect(issue.notes.first.downvote?).to be_falsey
- expect(issue.downvotes).to eq(0)
- end
-
- it "should recognize a single -1 note" do
- add_note "-1 This is bad"
- expect(issue.downvotes).to eq(1)
- end
-
- it "should recognize multiple -1 notes" do
- add_note('-1 This is bad', create(:user))
- add_note('-1 Away with this', create(:user))
- expect(issue.downvotes).to eq(2)
- end
- end
-
- describe "#votes_count" do
- it "with no notes has a 0/0 score" do
- expect(issue.votes_count).to eq(0)
- end
-
- it "should recognize non notes" do
- add_note "No +1 here"
- expect(issue.notes.size).to eq(1)
- expect(issue.votes_count).to eq(0)
- end
-
- it "should recognize a single +1 note" do
- add_note "+1 This is awesome"
- expect(issue.votes_count).to eq(1)
- end
-
- it "should recognize a single -1 note" do
- add_note "-1 This is bad"
- expect(issue.votes_count).to eq(1)
- end
-
- it "should recognize multiple notes" do
- add_note('+1 This is awesome', create(:user))
- add_note('-1 This is bad', create(:user))
- add_note('+1 I want this', create(:user))
- expect(issue.votes_count).to eq(3)
- end
-
- it 'should not count 2 -1 votes from the same user' do
- add_note '-1 This is suspicious'
- add_note '-1 This is bad'
- expect(issue.votes_count).to eq(1)
- end
- end
-
- describe "#upvotes_in_percent" do
- it "with no notes has a 0% score" do
- expect(issue.upvotes_in_percent).to eq(0)
- end
-
- it "should count a single 1 note as 100%" do
- add_note "+1 This is awesome"
- expect(issue.upvotes_in_percent).to eq(100)
- end
-
- it 'should count multiple +1 notes as 100%' do
- add_note('+1 This is awesome', create(:user))
- add_note('+1 I want this', create(:user))
- expect(issue.upvotes_in_percent).to eq(100)
- end
-
- it 'should count fractions for multiple +1 and -1 notes correctly' do
- add_note('+1 This is awesome', create(:user))
- add_note('+1 I want this', create(:user))
- add_note('-1 This is bad', create(:user))
- add_note('+1 me too', create(:user))
- expect(issue.upvotes_in_percent).to eq(75)
- end
- end
-
- describe "#downvotes_in_percent" do
- it "with no notes has a 0% score" do
- expect(issue.downvotes_in_percent).to eq(0)
- end
-
- it "should count a single -1 note as 100%" do
- add_note "-1 This is bad"
- expect(issue.downvotes_in_percent).to eq(100)
- end
-
- it 'should count multiple -1 notes as 100%' do
- add_note('-1 This is bad', create(:user))
- add_note('-1 Away with this', create(:user))
- expect(issue.downvotes_in_percent).to eq(100)
- end
-
- it 'should count fractions for multiple +1 and -1 notes correctly' do
- add_note('+1 This is awesome', create(:user))
- add_note('+1 I want this', create(:user))
- add_note('-1 This is bad', create(:user))
- add_note('+1 me too', create(:user))
- expect(issue.downvotes_in_percent).to eq(25)
- end
- end
-
- describe '#filter_superceded_votes' do
-
- it 'should count a users vote only once amongst multiple votes' do
- add_note('-1 This needs work before I will accept it')
- add_note('+1 I want this', create(:user))
- add_note('+1 This is is awesome', create(:user))
- add_note('+1 this looks good now')
- add_note('+1 This is awesome', create(:user))
- add_note('+1 me too', create(:user))
- expect(issue.downvotes).to eq(0)
- expect(issue.upvotes).to eq(5)
- end
-
- it 'should count each users vote only once' do
- add_note '-1 This needs work before it will be accepted'
- add_note '+1 I like this'
- add_note '+1 I still like this'
- add_note '+1 I really like this'
- add_note '+1 Give me this now!!!!'
- expect(issue.downvotes).to eq(0)
- expect(issue.upvotes).to eq(1)
- end
-
- it 'should count a users vote only once without caring about comments' do
- add_note '-1 This needs work before it will be accepted'
- add_note 'Comment 1'
- add_note 'Another comment'
- add_note '+1 vote'
- add_note 'final comment'
- expect(issue.downvotes).to eq(0)
- expect(issue.upvotes).to eq(1)
- end
-
- end
-
- def add_note(text, author = issue.author)
- created_at = Time.now - 1.hour + Note.count.seconds
- issue.notes << create(:note,
- note: text,
- project: issue.project,
- author_id: author.id,
- created_at: created_at)
- end
-end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 75564839dcf..f347f537550 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -32,77 +32,6 @@ describe Note do
it { is_expected.to validate_presence_of(:project) }
end
- describe '#votable?' do
- it 'is true for issue notes' do
- note = build(:note_on_issue)
- expect(note).to be_votable
- end
-
- it 'is true for merge request notes' do
- note = build(:note_on_merge_request)
- expect(note).to be_votable
- end
-
- it 'is false for merge request diff notes' do
- note = build(:note_on_merge_request_diff)
- expect(note).not_to be_votable
- end
-
- it 'is false for commit notes' do
- note = build(:note_on_commit)
- expect(note).not_to be_votable
- end
-
- it 'is false for commit diff notes' do
- note = build(:note_on_commit_diff)
- expect(note).not_to be_votable
- end
- end
-
- describe 'voting score' do
- it 'recognizes a neutral note' do
- note = build(:votable_note, note: 'This is not a +1 note')
- expect(note).not_to be_upvote
- expect(note).not_to be_downvote
- end
-
- it 'recognizes a neutral emoji note' do
- note = build(:votable_note, note: "I would :+1: this, but I don't want to")
- expect(note).not_to be_upvote
- expect(note).not_to be_downvote
- end
-
- it 'recognizes a +1 note' do
- note = build(:votable_note, note: '+1 for this')
- expect(note).to be_upvote
- end
-
- it 'recognizes a +1 emoji as a vote' do
- note = build(:votable_note, note: ':+1: for this')
- expect(note).to be_upvote
- end
-
- it 'recognizes a thumbsup emoji as a vote' do
- note = build(:votable_note, note: ':thumbsup: for this')
- expect(note).to be_upvote
- end
-
- it 'recognizes a -1 note' do
- note = build(:votable_note, note: '-1 for this')
- expect(note).to be_downvote
- end
-
- it 'recognizes a -1 emoji as a vote' do
- note = build(:votable_note, note: ':-1: for this')
- expect(note).to be_downvote
- end
-
- it 'recognizes a thumbsdown emoji as a vote' do
- note = build(:votable_note, note: ':thumbsdown: for this')
- expect(note).to be_downvote
- end
- end
-
describe "Commit notes" do
let!(:note) { create(:note_on_commit, note: "+1 from me") }
let!(:commit) { note.noteable }
@@ -139,10 +68,6 @@ describe Note do
it "should be recognized by #for_commit_diff_line?" do
expect(note).to be_for_commit_diff_line
end
-
- it "should not be votable" do
- expect(note).not_to be_votable
- end
end
describe 'authorization' do
@@ -204,4 +129,16 @@ describe Note do
it { expect(Note.search('wow')).to include(note) }
end
+
+ describe :grouped_awards do
+ before do
+ create :note, note: "smile", is_award: true
+ create :note, note: "smile", is_award: true
+ end
+
+ it "returns grouped array of notes" do
+ expect(Note.grouped_awards.first.first).to eq("smile")
+ expect(Note.grouped_awards.first.last).to match_array(Note.all)
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index c42e8870f8c..f80fada45e9 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -345,17 +345,6 @@ describe Project do
expect(project1.star_count).to eq(0)
expect(project2.star_count).to eq(0)
end
-
- it 'is decremented when an upvoter account is deleted' do
- user = create :user
- project = create :project, :public
- user.toggle_star(project)
- project.reload
- expect(project.star_count).to eq(1)
- user.destroy
- project.reload
- expect(project.star_count).to eq(0)
- end
end
describe :avatar_type do
diff --git a/spec/services/ci/create_commit_service_spec.rb b/spec/services/ci/create_commit_service_spec.rb
index e3a8fe9681b..e0ede1d58b7 100644
--- a/spec/services/ci/create_commit_service_spec.rb
+++ b/spec/services/ci/create_commit_service_spec.rb
@@ -53,7 +53,7 @@ module Ci
end
end
- it 'fails commits without .gitlab-ci.yml' do
+ it 'skips commits without .gitlab-ci.yml' do
stub_ci_commit_yaml_file(nil)
result = service.execute(project, user,
ref: 'refs/heads/0_1',
@@ -63,7 +63,24 @@ module Ci
)
expect(result).to be_persisted
expect(result.builds.any?).to be_falsey
- expect(result.status).to eq('failed')
+ expect(result.status).to eq('skipped')
+ expect(result.yaml_errors).to be_nil
+ end
+
+ it 'skips commits if yaml is invalid' do
+ message = 'message'
+ allow_any_instance_of(Ci::Commit).to receive(:git_commit_message) { message }
+ stub_ci_commit_yaml_file('invalid: file: file')
+ commits = [{ message: message }]
+ commit = service.execute(project, user,
+ ref: 'refs/tags/0_1',
+ before: '00000000',
+ after: '31das312',
+ commits: commits
+ )
+ expect(commit.builds.any?).to be false
+ expect(commit.status).to eq('failed')
+ expect(commit.yaml_errors).to_not be_nil
end
describe :ci_skip? do
@@ -100,7 +117,7 @@ module Ci
end
it "skips builds creation if there is [ci skip] tag in commit message and yaml is invalid" do
- stub_ci_commit_yaml_file('invalid: file')
+ stub_ci_commit_yaml_file('invalid: file: fiile')
commits = [{ message: message }]
commit = service.execute(project, user,
ref: 'refs/tags/0_1',
@@ -110,6 +127,7 @@ module Ci
)
expect(commit.builds.any?).to be false
expect(commit.status).to eq("skipped")
+ expect(commit.yaml_errors).to be_nil
end
end
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index f2ea0805b2f..cc38d257792 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -24,4 +24,38 @@ describe Notes::CreateService do
it { expect(@note.note).to eq('Awesome comment') }
end
end
+
+ describe "award emoji" do
+ before do
+ project.team << [user, :master]
+ end
+
+ it "creates emoji note" do
+ opts = {
+ note: ':smile: ',
+ noteable_type: 'Issue',
+ noteable_id: issue.id
+ }
+
+ @note = Notes::CreateService.new(project, user, opts).execute
+
+ expect(@note).to be_valid
+ expect(@note.note).to eq('smile')
+ expect(@note.is_award).to be_truthy
+ end
+
+ it "creates regular note if emoji name is invalid" do
+ opts = {
+ note: ':smile: moretext: ',
+ noteable_type: 'Issue',
+ noteable_id: issue.id
+ }
+
+ @note = Notes::CreateService.new(project, user, opts).execute
+
+ expect(@note).to be_valid
+ expect(@note.note).to eq(opts[:note])
+ expect(@note.is_award).to be_falsy
+ end
+ end
end