diff options
55 files changed, 870 insertions, 269 deletions
diff --git a/CHANGELOG b/CHANGELOG index 94bccb729dd..dc8dbb6e166 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.12.0 (unreleased) - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 + - Only check :can_resolve permission if the note is resolvable - Add ability to fork to a specific namespace using API. (ritave) - Cleanup misalignments in Issue list view !6206 - Prune events older than 12 months. (ritave) @@ -15,9 +16,13 @@ v 8.12.0 (unreleased) - Add font color contrast to external label in admin area (ClemMakesApps) - Change logo animation to CSS (ClemMakesApps) - Instructions for enabling Git packfile bitmaps !6104 + - Use Search::GlobalService.new in the `GET /projects/search/:query` endpoint - Fix pagination on user snippets page + - Fix sorting of issues in API + - Ensure specs on sorting of issues in API are deterministic on MySQL - Escape search term before passing it to Regexp.new !6241 (winniehell) - Fix pinned sidebar behavior in smaller viewports !6169 + - Fix file permissions change when updating a file on the Gitlab UI !5979 - Change merge_error column from string to text type - Reduce contributions calendar data payload (ClemMakesApps) - Add `web_url` field to issue, merge request, and snippet API objects (Ben Boeckel) @@ -85,6 +90,7 @@ v 8.12.0 (unreleased) - Fix repo title alignment (ClemMakesApps) - Change update interval of contacted_at - Fix branch title trailing space on hover (ClemMakesApps) + - Don't include 'Created By' tag line when importing from GitHub if there is a linked GitLab account (EspadaV8) - Award emoji tooltips containing more than 10 usernames are now truncated !4780 (jlogandavison) - Fix duplicate "me" in award emoji tooltip !5218 (jlogandavison) - Order award emoji tooltips in order they were added (EspadaV8) @@ -109,6 +115,7 @@ v 8.12.0 (unreleased) - Avoid conflict with admin labels when importing GitHub labels - User can edit closed MR with deleted fork (Katarzyna Kobierska Ula Budziszewska) !5496 - Fix repository page ui issues + - Add information about user and manual build start to runner as variables !6201 (Sergey Gnuskov) - Fixed invisible scroll controls on build page on iPhone - Fix error on raw build trace download for old builds stored in database !4822 - Refactor the triggers page and documentation !6217 @@ -116,8 +123,11 @@ v 8.12.0 (unreleased) - Use default clone protocol on "check out, review, and merge locally" help page URL - API for Ci Lint !5953 (Katarzyna Kobierska Urszula Budziszewska) - Allow bulk update merge requests from merge requests index page + - Add notification_settings API calls !5632 (mahcsig) + - Remove duplication between project builds and admin builds view !5680 (Katarzyna Kobierska Ula Budziszewska) v 8.11.6 (unreleased) + - Fix an error where we were unable to create a CommitStatus for running state v 8.11.5 - Optimize branch lookups and force a repository reload for Repository#find_branch. !6087 diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 367c7d01944..76b93b23b95 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -79,10 +79,6 @@ padding-left: 15px !important; } - .issue-info, .merge-request-info { - display: none; - } - .nav-links, .nav-links { li a { font-size: 14px; diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 7a26b7ad497..60a0d50ba73 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -37,6 +37,15 @@ form.edit-issue { margin: 0; } +ul.related-merge-requests > li { + display: -ms-flexbox; + display: -webkit-flex; + display: flex; + .merge-request-id { + flex-shrink: 0; + } +} + .merge-requests-title, .related-branches-title { font-size: 16px; font-weight: 600; diff --git a/app/controllers/concerns/creates_commit.rb b/app/controllers/concerns/creates_commit.rb index f2b8f297bc2..dacb5679dd3 100644 --- a/app/controllers/concerns/creates_commit.rb +++ b/app/controllers/concerns/creates_commit.rb @@ -7,8 +7,7 @@ module CreatesCommit commit_params = @commit_params.merge( source_project: @project, source_branch: @ref, - target_branch: @target_branch, - previous_path: @previous_path + target_branch: @target_branch ) result = service.new(@tree_edit_project, current_user, commit_params).execute diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index cdf9a04bacf..b78cc6585ba 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -38,12 +38,7 @@ class Projects::BlobController < Projects::ApplicationController end def update - if params[:file_path].present? - @previous_path = @path - @path = params[:file_path] - @commit_params[:file_path] = @path - end - + @path = params[:file_path] if params[:file_path].present? after_edit_path = if from_merge_request && @target_branch == @ref diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + @@ -143,6 +138,8 @@ class Projects::BlobController < Projects::ApplicationController params[:file_name] = params[:file].original_filename end File.join(@path, params[:file_name]) + elsif params[:file_path].present? + params[:file_path] else @path end @@ -155,6 +152,7 @@ class Projects::BlobController < Projects::ApplicationController @commit_params = { file_path: @file_path, commit_message: params[:commit_message], + previous_path: @path, file_content: params[:content], file_content_encoding: params[:encoding], last_commit_sha: params[:last_commit_sha] diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 61052437318..fb16bc06d71 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -148,6 +148,7 @@ module Ci variables += runner.predefined_variables if runner variables += project.container_registry_variables variables += yaml_variables + variables += user_variables variables += project.secret_variables variables += trigger_request.user_variables if trigger_request variables @@ -434,6 +435,15 @@ module Ci read_attribute(:yaml_variables) || build_attributes_from_config[:yaml_variables] || [] end + def user_variables + return [] if user.blank? + + [ + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true } + ] + end + private def update_artifacts_size @@ -469,6 +479,7 @@ module Ci ] variables << { key: 'CI_BUILD_TAG', value: ref, public: true } if tag? variables << { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true } if trigger_request + variables << { key: 'CI_BUILD_MANUAL', value: 'true', public: true } if manual? variables end diff --git a/app/models/project.rb b/app/models/project.rb index a6de2c48071..4017cabe9f0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -58,7 +58,7 @@ class Project < ActiveRecord::Base # Relations belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' - belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' + belongs_to :group, -> { where(type: 'Group') }, foreign_key: 'namespace_id' belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' diff --git a/app/models/repository.rb b/app/models/repository.rb index 7b7090b8a73..3c354c25c6f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -813,7 +813,7 @@ class Repository update: true } - if previous_path + if previous_path && previous_path != path options[:file][:previous_path] = previous_path Gitlab::Git::Blob.rename(raw_repository, options) else diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index de48a50774e..36c93dddadb 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -31,13 +31,13 @@ module Ci current_status = status_for_prior_stages(index) created_builds_in_stage(index).select do |build| - process_build(build, current_status) + if HasStatus::COMPLETED_STATUSES.include?(current_status) + process_build(build, current_status) + end end end def process_build(build, current_status) - return false unless HasStatus::COMPLETED_STATUSES.include?(current_status) - if valid_statuses_for_when(build.when).include?(current_status) build.enqueue true diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml deleted file mode 100644 index f29d9c94441..00000000000 --- a/app/views/admin/builds/_build.html.haml +++ /dev/null @@ -1,77 +0,0 @@ -- project = build.project -%tr.build.commit - %td.status - = ci_status_with_icon(build.status) - - %td - .branch-commit - - if can?(current_user, :read_build, build.project) - = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %span.build-link ##{build.id} - - else - %span.build-link ##{build.id} - - - if build.ref - .icon-container - = build.tag? ? icon('tag') : icon('code-fork') - = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - - else - .light none - .icon-container - = custom_icon("icon_commit") - - = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id" - - if build.stuck? - %i.fa.fa-warning.text-warning - - .label-container - - if build.tags.any? - - build.tags.each do |tag| - %span.label.label-primary - = tag - - if build.try(:trigger_request) - %span.label.label-info triggered - - if build.try(:allow_failure) - %span.label.label-danger allowed to fail - - %td - - if project - = link_to project.name_with_namespace, admin_namespace_project_path(project.namespace, project) - - %td - - if build.try(:runner) - = runner_link(build.runner) - - else - .light none - - %td - #{build.stage} / #{build.name} - - %td - - if build.duration - %p.duration - = custom_icon("icon_timer") - = duration_in_numbers(build.duration) - - - if build.finished_at - %p.finished-at - = icon("calendar") - %span #{time_ago_with_tooltip(build.finished_at)} - - - if defined?(coverage) && coverage - %td.coverage - - if build.try(:coverage) - #{build.coverage}% - - %td - .pull-right - - if can?(current_user, :read_build, project) && build.artifacts? - = link_to download_namespace_project_build_artifacts_path(build.project.namespace, build.project, build), title: 'Download artifacts', class: 'btn btn-build' do - %i.fa.fa-download - - if can?(current_user, :update_build, build.project) - - if build.active? - = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do - %i.fa.fa-remove.cred - - elsif defined?(allow_retry) && allow_retry && build.retryable? - = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do - %i.fa.fa-refresh diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml index 3d77634d8fa..26a8846b609 100644 --- a/app/views/admin/builds/index.html.haml +++ b/app/views/admin/builds/index.html.haml @@ -4,26 +4,8 @@ %div{ class: container_class } .top-area - %ul.nav-links - %li{class: ('active' if @scope.nil?)} - = link_to admin_builds_path do - All - %span.badge.js-totalbuilds-count= @all_builds.count(:id) - - %li{class: ('active' if @scope == 'pending')} - = link_to admin_builds_path(scope: :pending) do - Pending - %span.badge= number_with_delimiter(@all_builds.pending.count(:id)) - - %li{class: ('active' if @scope == 'running')} - = link_to admin_builds_path(scope: :running) do - Running - %span.badge= number_with_delimiter(@all_builds.running.count(:id)) - - %li{class: ('active' if @scope == 'finished')} - = link_to admin_builds_path(scope: :finished) do - Finished - %span.badge= number_with_delimiter(@all_builds.finished.count(:id)) + - build_path_proc = ->(scope) { admin_builds_path(scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope .nav-controls - if @all_builds.running_or_pending.any? @@ -33,23 +15,4 @@ #{(@scope || 'all').capitalize} builds %ul.content-list.builds-content-list - - if @builds.blank? - %li - .nothing-here-block No builds to show - - else - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Commit - %th Project - %th Runner - %th Name - %th - %th - - - @builds.each do |build| - = render "admin/builds/build", build: build - - = paginate @builds, theme: 'gitlab' + = render "projects/builds/table", builds: @builds, admin: true diff --git a/app/views/projects/builds/_table.html.haml b/app/views/projects/builds/_table.html.haml new file mode 100644 index 00000000000..61eff73da26 --- /dev/null +++ b/app/views/projects/builds/_table.html.haml @@ -0,0 +1,24 @@ +- admin = local_assigns.fetch(:admin, false) + +- if builds.blank? + %li + .nothing-here-block No builds to show +- else + .table-holder + %table.table.builds + %thead + %tr + %th Status + %th Commit + - if admin + %th Project + %th Runner + %th Stage + %th Name + %th + %th Coverage + %th + + = render partial: "projects/ci/builds/build", collection: builds, as: :build, locals: { commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: admin || project.build_coverage_enabled?, admin: admin } + + = paginate builds, theme: 'gitlab' diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml index 2af625f69cd..5c60b7a7364 100644 --- a/app/views/projects/builds/index.html.haml +++ b/app/views/projects/builds/index.html.haml @@ -4,30 +4,8 @@ %div{ class: container_class } .top-area - %ul.nav-links - %li{class: ('active' if @scope.nil?)} - = link_to project_builds_path(@project) do - All - %span.badge.js-totalbuilds-count - = number_with_delimiter(@all_builds.count(:id)) - - %li{class: ('active' if @scope == 'pending')} - = link_to project_builds_path(@project, scope: :pending) do - Pending - %span.badge - = number_with_delimiter(@all_builds.pending.count(:id)) - - %li{class: ('active' if @scope == 'running')} - = link_to project_builds_path(@project, scope: :running) do - Running - %span.badge - = number_with_delimiter(@all_builds.running.count(:id)) - - %li{class: ('active' if @scope == 'finished')} - = link_to project_builds_path(@project, scope: :finished) do - Finished - %span.badge - = number_with_delimiter(@all_builds.finished.count(:id)) + - build_path_proc = ->(scope) { project_builds_path(@project, scope: scope) } + = render "shared/builds/tabs", build_path_proc: build_path_proc, all_builds: @all_builds, scope: @scope .nav-controls - if can?(current_user, :update_build, @project) @@ -42,23 +20,4 @@ %span CI Lint %ul.content-list.builds-content-list - - if @builds.blank? - %li - .nothing-here-block No builds to show - - else - .table-holder - %table.table.builds - %thead - %tr - %th Status - %th Commit - %th Stage - %th Name - %th - - if @project.build_coverage_enabled? - %th Coverage - %th - - = render @builds, commit_sha: true, ref: true, stage: true, allow_retry: true, coverage: @project.build_coverage_enabled? - - = paginate @builds, theme: 'gitlab' + = render "table", builds: @builds, project: @project diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 73de8abe55b..75192c48188 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -1,3 +1,11 @@ +- admin = local_assigns.fetch(:admin, false) +- ref = local_assigns.fetch(:ref, nil) +- commit_sha = local_assigns.fetch(:commit_sha, nil) +- retried = local_assigns.fetch(:retried, false) +- stage = local_assigns.fetch(:stage, false) +- coverage = local_assigns.fetch(:coverage, false) +- allow_retry = local_assigns.fetch(:allow_retry, false) + %tr.build.commit %td.status - if can?(current_user, :read_build, build) @@ -9,11 +17,11 @@ .branch-commit - if can?(current_user, :read_build, build) = link_to namespace_project_build_url(build.project.namespace, build.project, build) do - %span ##{build.id} + %span.build-link ##{build.id} - else - %span ##{build.id} + %span.build-link ##{build.id} - - if defined?(ref) && ref + - if ref - if build.ref .icon-container = build.tag? ? icon('tag') : icon('code-fork') @@ -23,12 +31,12 @@ .icon-container = custom_icon("icon_commit") - - if defined?(commit_sha) && commit_sha + - if commit_sha = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" - if build.stuck? = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - - if defined?(retried) && retried + - if retried = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') .label-container @@ -40,19 +48,24 @@ %span.label.label-info triggered - if build.try(:allow_failure) %span.label.label-danger allowed to fail - - if defined?(retried) && retried + - if retried %span.label.label-warning retried - if build.manual? %span.label.label-info manual - - if defined?(runner) && runner + - if admin + %td + - if build.project + = link_to build.project.name_with_namespace, admin_namespace_project_path(build.project.namespace, build.project) + + - if admin %td - if build.try(:runner) = runner_link(build.runner) - else .light none - - if defined?(stage) && stage + - if stage %td = build.stage @@ -64,13 +77,14 @@ %p.duration = custom_icon("icon_timer") = duration_in_numbers(build.duration) + - if build.finished_at %p.finished-at = icon("calendar") %span #{time_ago_with_tooltip(build.finished_at)} - - if defined?(coverage) && coverage - %td.coverage + %td.coverage + - if coverage - if build.try(:coverage) #{build.coverage}% @@ -83,10 +97,10 @@ - if build.active? = link_to cancel_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do = icon('remove', class: 'cred') - - elsif defined?(allow_retry) && allow_retry + - elsif allow_retry - if build.retryable? = link_to retry_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Retry', class: 'btn btn-build' do = icon('repeat') - - elsif build.playable? + - elsif build.playable? && !admin = link_to play_namespace_project_build_path(build.project.namespace, build.project, build, return_to: request.original_url), method: :post, title: 'Play', class: 'btn btn-build' do = custom_icon('icon_play') diff --git a/app/views/projects/issues/_merge_requests.html.haml b/app/views/projects/issues/_merge_requests.html.haml index d8075371853..31d3ec23276 100644 --- a/app/views/projects/issues/_merge_requests.html.haml +++ b/app/views/projects/issues/_merge_requests.html.haml @@ -1,7 +1,7 @@ - if @merge_requests.any? %h2.merge-requests-title = pluralize(@merge_requests.count, 'Related Merge Request') - %ul.unstyled-list + %ul.unstyled-list.related-merge-requests - has_any_ci = @merge_requests.any?(&:pipeline) - @merge_requests.each do |merge_request| %li diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index a8eeab3e55e..44683c8bcdb 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -1,7 +1,7 @@ - if @related_branches.any? %h2.related-branches-title = pluralize(@related_branches.count, 'Related Branch') - %ul.unstyled-list + %ul.unstyled-list.related-merge-requests - @related_branches.each do |branch| %li - target = @project.repository.find_branch(branch).target diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 7c82177f9ea..9ec17cf6e76 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -1,6 +1,5 @@ - return unless note.author - return if note.cross_reference_not_visible_for?(current_user) -- can_resolve = can?(current_user, :resolve_note, note) - note_editable = note_editable?(note) %li.timeline-entry{ id: dom_id(note), class: ["note", "note-row-#{note.id}", ('system-note' if note.system)], data: {author_id: note.author.id, editable: note_editable} } @@ -24,6 +23,8 @@ %span.note-role.hidden-xs= access - if note.resolvable? + - can_resolve = can?(current_user, :resolve_note, note) + %resolve-btn{ ":namespace-path" => "'#{note.project.namespace.path}'", ":project-path" => "'#{note.project.path}'", ":discussion-id" => "'#{note.discussion_id}'", diff --git a/app/views/shared/builds/_tabs.html.haml b/app/views/shared/builds/_tabs.html.haml new file mode 100644 index 00000000000..60353aee7f1 --- /dev/null +++ b/app/views/shared/builds/_tabs.html.haml @@ -0,0 +1,24 @@ +%ul.nav-links + %li{ class: ('active' if scope.nil?) } + = link_to build_path_proc.call(nil) do + All + %span.badge.js-totalbuilds-count + = number_with_delimiter(all_builds.count(:id)) + + %li{ class: ('active' if scope == 'pending') } + = link_to build_path_proc.call('pending') do + Pending + %span.badge + = number_with_delimiter(all_builds.pending.count(:id)) + + %li{ class: ('active' if scope == 'running') } + = link_to build_path_proc.call('running') do + Running + %span.badge + = number_with_delimiter(all_builds.running.count(:id)) + + %li{ class: ('active' if scope == 'finished') } + = link_to build_path_proc.call('finished') do + Finished + %span.badge + = number_with_delimiter(all_builds.finished.count(:id)) diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index f498732feca..5e3e4c966cb 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -13,9 +13,5 @@ Mime::Type.register "video/mp4", :mp4, [], [:m4v, :mov] Mime::Type.register "video/webm", :webm Mime::Type.register "video/ogg", :ogv -middlewares = Gitlab::Application.config.middleware -middlewares.swap(ActionDispatch::ParamsParser, ActionDispatch::ParamsParser, { - Mime::Type.lookup('application/vnd.git-lfs+json') => lambda do |body| - ActiveSupport::JSON.decode(body) - end -}) +Mime::Type.unregister :json +Mime::Type.register 'application/json', :json, %w(application/vnd.git-lfs+json application/json) diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md index 28c4c7c86ca..c5611e2a121 100644 --- a/doc/administration/container_registry.md +++ b/doc/administration/container_registry.md @@ -406,7 +406,8 @@ To configure the storage driver in Omnibus: 's3' => { 'accesskey' => 's3-access-key', 'secretkey' => 's3-secret-key-for-access-key', - 'bucket' => 'your-s3-bucket' + 'bucket' => 'your-s3-bucket', + 'region' => 'your-s3-region' } } ``` @@ -428,6 +429,7 @@ storage: accesskey: 'AKIAKIAKI' secretkey: 'secret123' bucket: 'gitlab-registry-bucket-AKIAKIAKI' + region: 'your-s3-region' cache: blobdescriptor: inmemory delete: diff --git a/doc/api/README.md b/doc/api/README.md index e12070dc1ce..7661e1eea02 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -27,6 +27,7 @@ following locations: - [Open source license templates](licenses.md) - [Namespaces](namespaces.md) - [Notes](notes.md) (comments) +- [Notification settings](notification_settings.md) - [Pipelines](pipelines.md) - [Projects](projects.md) including setting Webhooks - [Project Access Requests](access_requests.md) diff --git a/doc/api/notification_settings.md b/doc/api/notification_settings.md new file mode 100644 index 00000000000..ff6c9e4931c --- /dev/null +++ b/doc/api/notification_settings.md @@ -0,0 +1,169 @@ +# Notification settings + +>**Note:** This feature was [introduced][ce-5632] in GitLab 8.12. + +**Valid notification levels** + +The notification levels are defined in the `NotificationSetting::level` model enumeration. Currently, these levels are recognized: + +``` +disabled +participating +watch +global +mention +custom +``` + +If the `custom` level is used, specific email events can be controlled. Notification email events are defined in the `NotificationSetting::EMAIL_EVENTS` model variable. Currently, these events are recognized: + +``` +new_note +new_issue +reopen_issue +close_issue +reassign_issue +new_merge_request +reopen_merge_request +close_merge_request +reassign_merge_request +merge_merge_request +``` + +## Global notification settings + +Get current notification settings and email address. + +``` +GET /notification_settings +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/notification_settings +``` + +Example response: + +```json +{ + "level": "participating", + "notification_email": "admin@example.com" +} +``` + +## Update global notification settings + +Update current notification settings and email address. + +``` +PUT /notification_settings +``` + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/notification_settings?level=watch +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `level` | string | no | The global notification level | +| `notification_email` | string | no | The email address to send notifications | +| `new_note` | boolean | no | Enable/disable this notification | +| `new_issue` | boolean | no | Enable/disable this notification | +| `reopen_issue` | boolean | no | Enable/disable this notification | +| `close_issue` | boolean | no | Enable/disable this notification | +| `reassign_issue` | boolean | no | Enable/disable this notification | +| `new_merge_request` | boolean | no | Enable/disable this notification | +| `reopen_merge_request` | boolean | no | Enable/disable this notification | +| `close_merge_request` | boolean | no | Enable/disable this notification | +| `reassign_merge_request` | boolean | no | Enable/disable this notification | +| `merge_merge_request` | boolean | no | Enable/disable this notification | + +Example response: + +```json +{ + "level": "watch", + "notification_email": "admin@example.com" +} +``` + +## Group / project level notification settings + +Get current group or project notification settings. + +``` +GET /groups/:id/notification_settings +GET /projects/:id/notification_settings +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/5/notification_settings +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/notification_settings +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The group/project ID or path | + +Example response: + +```json +{ + "level": "global" +} +``` + +## Update group/project level notification settings + +Update current group/project notification settings. + +``` +PUT /groups/:id/notification_settings +PUT /projects/:id/notification_settings +``` + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/groups/5/notification_settings?level=watch +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/8/notification_settings?level=custom&new_note=true +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The group/project ID or path | +| `level` | string | no | The global notification level | +| `new_note` | boolean | no | Enable/disable this notification | +| `new_issue` | boolean | no | Enable/disable this notification | +| `reopen_issue` | boolean | no | Enable/disable this notification | +| `close_issue` | boolean | no | Enable/disable this notification | +| `reassign_issue` | boolean | no | Enable/disable this notification | +| `new_merge_request` | boolean | no | Enable/disable this notification | +| `reopen_merge_request` | boolean | no | Enable/disable this notification | +| `close_merge_request` | boolean | no | Enable/disable this notification | +| `reassign_merge_request` | boolean | no | Enable/disable this notification | +| `merge_merge_request` | boolean | no | Enable/disable this notification | + +Example responses: + +```json +{ + "level": "watch" +} + +{ + "level": "custom", + "events": { + "new_note": true, + "new_issue": false, + "reopen_issue": false, + "close_issue": false, + "reassign_issue": false, + "new_merge_request": false, + "reopen_merge_request": false, + "close_merge_request": false, + "reassign_merge_request": false, + "merge_merge_request": false + } +} +``` + +[ce-5632]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5632 diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index c32831d3aaa..6a971c3ae87 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -34,6 +34,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_BUILD_REF_NAME** | all | all | The branch or tag name for which project is built | | **CI_BUILD_REPO** | all | all | The URL to clone the Git repository | | **CI_BUILD_TRIGGERED** | all | 0.5 | The flag to indicate that build was [triggered] | +| **CI_BUILD_MANUAL** | 8.12 | all | The flag to indicate that build was manually started | | **CI_BUILD_TOKEN** | all | 1.2 | Token used for authenticating with the GitLab Container Registry | | **CI_PIPELINE_ID** | 8.10 | 0.5 | The unique id of the current pipeline that GitLab CI uses internally | | **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally | @@ -47,6 +48,8 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | +| **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | **Some of the variables are only available when using runner with at least defined version.** @@ -60,6 +63,7 @@ export CI_BUILD_REPO="https://gitab-ci-token:abcde-1234ABCD5678ef@gitlab.com/git export CI_BUILD_TAG="1.0.0" export CI_BUILD_NAME="spec:other" export CI_BUILD_STAGE="test" +export CI_BUILD_MANUAL="true" export CI_BUILD_TRIGGERED="true" export CI_BUILD_TOKEN="abcde-1234ABCD5678ef" export CI_PIPELINE_ID="1000" @@ -78,6 +82,8 @@ export CI_SERVER="yes" export CI_SERVER_NAME="GitLab" export CI_SERVER_REVISION="70606bf" export CI_SERVER_VERSION="8.9.0" +export GITLAB_USER_ID="42" +export GITLAB_USER_EMAIL="alexzander@sporer.com" ``` ### YAML-defined variables diff --git a/doc/development/instrumentation.md b/doc/development/instrumentation.md index c2272ab0a2b..105e2f1242a 100644 --- a/doc/development/instrumentation.md +++ b/doc/development/instrumentation.md @@ -137,3 +137,18 @@ end ``` Here the final value of `sleep_real_time` will be `3`, _not_ `1`. + +## Tracking Custom Events + +Besides instrumenting code GitLab Performance Monitoring also supports tracking +of custom events. This is primarily intended to be used for tracking business +metrics such as the number of Git pushes, repository imports, and so on. + +To track a custom event simply call `Gitlab::Metrics.add_event` passing it an +event name and a custom set of (optional) tags. For example: + +```ruby +Gitlab::Metrics.add_event(:user_login, email: current_user.email) +``` + +Event names should be verbs such as `push_repository` and `remove_branch`. diff --git a/lib/api/api.rb b/lib/api/api.rb index a08fb056049..74ca4728695 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -51,6 +51,7 @@ module API mount ::API::Milestones mount ::API::Namespaces mount ::API::Notes + mount ::API::NotificationSettings mount ::API::Pipelines mount ::API::ProjectHooks mount ::API::ProjectSnippets diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb index 5e3c9563703..dfbdd597d29 100644 --- a/lib/api/commit_statuses.rb +++ b/lib/api/commit_statuses.rb @@ -37,7 +37,7 @@ module API # id (required) - The ID of a project # sha (required) - The commit hash # ref (optional) - The ref - # state (required) - The state of the status. Can be: pending, running, success, error or failure + # state (required) - The state of the status. Can be: pending, running, success, failed or canceled # target_url (optional) - The target URL to associate with this status # description (optional) - A short description of the status # name or context (optional) - A string label to differentiate this status from the status of other systems. Default: "default" @@ -46,7 +46,7 @@ module API post ':id/statuses/:sha' do authorize! :create_commit_status, user_project required_attributes! [:state] - attrs = attributes_for_keys [:ref, :target_url, :description, :context, :name] + attrs = attributes_for_keys [:target_url, :description] commit = @project.commit(params[:sha]) not_found! 'Commit' unless commit @@ -58,36 +58,38 @@ module API # the first found branch on that commit ref = params[:ref] - unless ref - branches = @project.repository.branch_names_contains(commit.sha) - not_found! 'References for commit' if branches.none? - ref = branches.first - end + ref ||= @project.repository.branch_names_contains(commit.sha).first + not_found! 'References for commit' unless ref + + name = params[:name] || params[:context] || 'default' pipeline = @project.ensure_pipeline(ref, commit.sha, current_user) - name = params[:name] || params[:context] - status = GenericCommitStatus.running_or_pending.find_by(pipeline: pipeline, name: name, ref: params[:ref]) - status ||= GenericCommitStatus.new(project: @project, pipeline: pipeline, user: current_user) - status.update(attrs) + status = GenericCommitStatus.running_or_pending.find_or_initialize_by( + project: @project, pipeline: pipeline, + user: current_user, name: name, ref: ref) + status.attributes = attrs - case params[:state].to_s - when 'running' - status.run - when 'success' - status.success - when 'failed' - status.drop - when 'canceled' - status.cancel - else - status.status = params[:state].to_s - end + begin + case params[:state].to_s + when 'pending' + status.enqueue! + when 'running' + status.enqueue + status.run! + when 'success' + status.success! + when 'failed' + status.drop! + when 'canceled' + status.cancel! + else + render_api_error!('invalid state', 400) + end - if status.save present status, with: Entities::CommitStatus - else - render_validation_error!(status) + rescue StateMachines::InvalidTransition => e + render_api_error!(e.message, 400) end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3faba79415b..4f736e4ec2b 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -375,7 +375,7 @@ module API expose :access_level expose :notification_level do |member, options| if member.notification_setting - NotificationSetting.levels[member.notification_setting.level] + ::NotificationSetting.levels[member.notification_setting.level] end end end @@ -386,6 +386,21 @@ module API class GroupAccess < MemberAccess end + class NotificationSetting < Grape::Entity + expose :level + expose :events, if: ->(notification_setting, _) { notification_setting.custom? } do + ::NotificationSetting::EMAIL_EVENTS.each do |event| + expose event + end + end + end + + class GlobalNotificationSetting < NotificationSetting + expose :notification_email do |notification_setting, options| + notification_setting.user.notification_email + end + end + class ProjectService < Grape::Entity expose :id, :title, :created_at, :updated_at, :active expose :push_events, :issues_events, :merge_requests_events diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 556684187d8..c9689e6f8ef 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -41,7 +41,8 @@ module API issues = current_user.issues.inc_notes_with_associations issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? - issues.reorder(issuable_order_by => issuable_sort) + issues = issues.reorder(issuable_order_by => issuable_sort) + present paginate(issues), with: Entities::Issue, current_user: current_user end end @@ -73,7 +74,11 @@ module API params[:group_id] = group.id params[:milestone_title] = params.delete(:milestone) params[:label_name] = params.delete(:labels) - params[:sort] = "#{params.delete(:order_by)}_#{params.delete(:sort)}" if params[:order_by] && params[:sort] + + if params[:order_by] || params[:sort] + # The Sortable concern takes 'created_desc', not 'created_at_desc' (for example) + params[:sort] = "#{issuable_order_by.sub('_at', '')}_#{issuable_sort}" + end issues = IssuesFinder.new(current_user, params).execute @@ -113,7 +118,8 @@ module API issues = filter_issues_milestone(issues, params[:milestone]) end - issues.reorder(issuable_order_by => issuable_sort) + issues = issues.reorder(issuable_order_by => issuable_sort) + present paginate(issues), with: Entities::Issue, current_user: current_user end diff --git a/lib/api/notification_settings.rb b/lib/api/notification_settings.rb new file mode 100644 index 00000000000..a70a7e71073 --- /dev/null +++ b/lib/api/notification_settings.rb @@ -0,0 +1,97 @@ +module API + # notification_settings API + class NotificationSettings < Grape::API + before { authenticate! } + + helpers ::API::Helpers::MembersHelpers + + resource :notification_settings do + desc 'Get global notification level settings and email, defaults to Participate' do + detail 'This feature was introduced in GitLab 8.12' + success Entities::GlobalNotificationSetting + end + get do + notification_setting = current_user.global_notification_setting + + present notification_setting, with: Entities::GlobalNotificationSetting + end + + desc 'Update global notification level settings and email, defaults to Participate' do + detail 'This feature was introduced in GitLab 8.12' + success Entities::GlobalNotificationSetting + end + params do + optional :level, type: String, desc: 'The global notification level' + optional :notification_email, type: String, desc: 'The email address to send notifications' + NotificationSetting::EMAIL_EVENTS.each do |event| + optional event, type: Boolean, desc: 'Enable/disable this notification' + end + end + put do + notification_setting = current_user.global_notification_setting + + begin + notification_setting.transaction do + new_notification_email = params.delete(:notification_email) + declared_params = declared(params, include_missing: false).to_h + + current_user.update(notification_email: new_notification_email) if new_notification_email + notification_setting.update(declared_params) + end + rescue ArgumentError => e # catch level enum error + render_api_error! e.to_s, 400 + end + + render_validation_error! current_user + render_validation_error! notification_setting + present notification_setting, with: Entities::GlobalNotificationSetting + end + end + + %w[group project].each do |source_type| + resource source_type.pluralize do + desc "Get #{source_type} level notification level settings, defaults to Global" do + detail 'This feature was introduced in GitLab 8.12' + success Entities::NotificationSetting + end + params do + requires :id, type: String, desc: 'The group ID or project ID or project NAMESPACE/PROJECT_NAME' + end + get ":id/notification_settings" do + source = find_source(source_type, params[:id]) + + notification_setting = current_user.notification_settings_for(source) + + present notification_setting, with: Entities::NotificationSetting + end + + desc "Update #{source_type} level notification level settings, defaults to Global" do + detail 'This feature was introduced in GitLab 8.12' + success Entities::NotificationSetting + end + params do + requires :id, type: String, desc: 'The group ID or project ID or project NAMESPACE/PROJECT_NAME' + optional :level, type: String, desc: "The #{source_type} notification level" + NotificationSetting::EMAIL_EVENTS.each do |event| + optional event, type: Boolean, desc: 'Enable/disable this notification' + end + end + put ":id/notification_settings" do + source = find_source(source_type, params.delete(:id)) + notification_setting = current_user.notification_settings_for(source) + + begin + declared_params = declared(params, include_missing: false).to_h + + notification_setting.update(declared_params) + rescue ArgumentError => e # catch level enum error + render_api_error! e.to_s, 400 + end + + render_validation_error! notification_setting + present notification_setting, with: Entities::NotificationSetting + end + end + end + end +end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 4033f597859..644d836ed0b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -428,18 +428,9 @@ module API # Example Request: # GET /projects/search/:query get "/search/:query" do - ids = current_user.authorized_projects.map(&:id) - visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] - projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%") - sort = params[:sort] == 'desc' ? 'desc' : 'asc' - - projects = case params["order_by"] - when 'id' then projects.order("id #{sort}") - when 'name' then projects.order("name #{sort}") - when 'created_at' then projects.order("created_at #{sort}") - when 'last_activity_at' then projects.order("last_activity_at #{sort}") - else projects - end + search_service = Search::GlobalService.new(current_user, search: params[:query]).execute + projects = search_service.objects('projects', params[:page]) + projects = projects.reorder(project_order_by => project_sort) present paginate(projects), with: Entities::Project end diff --git a/lib/gitlab/github_import/base_formatter.rb b/lib/gitlab/github_import/base_formatter.rb index d546e102c63..8cacf4f4925 100644 --- a/lib/gitlab/github_import/base_formatter.rb +++ b/lib/gitlab/github_import/base_formatter.rb @@ -20,6 +20,11 @@ module Gitlab find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s). try(:id) end + + def gitlab_author_id + return @gitlab_author_id if defined?(@gitlab_author_id) + @gitlab_author_id = gitlab_user_id(raw_data.user.id) + end end end end diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb index 1c7c1a73c77..2bddcde2b7c 100644 --- a/lib/gitlab/github_import/comment_formatter.rb +++ b/lib/gitlab/github_import/comment_formatter.rb @@ -21,7 +21,7 @@ module Gitlab end def author_id - gitlab_user_id(raw_data.user.id) || project.creator_id + gitlab_author_id || project.creator_id end def body @@ -52,7 +52,11 @@ module Gitlab end def note - formatter.author_line(author) + body + if gitlab_author_id + body + else + formatter.author_line(author) + body + end end def type diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb index ad4f1d8ae99..77621de9f4c 100644 --- a/lib/gitlab/github_import/issue_formatter.rb +++ b/lib/gitlab/github_import/issue_formatter.rb @@ -49,7 +49,7 @@ module Gitlab end def author_id - gitlab_user_id(raw_data.user.id) || project.creator_id + gitlab_author_id || project.creator_id end def body @@ -57,7 +57,11 @@ module Gitlab end def description - @formatter.author_line(author) + body + if gitlab_author_id + body + else + formatter.author_line(author) + body + end end def milestone diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb index 87e031b27f8..1408683100f 100644 --- a/lib/gitlab/github_import/pull_request_formatter.rb +++ b/lib/gitlab/github_import/pull_request_formatter.rb @@ -77,7 +77,7 @@ module Gitlab end def author_id - gitlab_user_id(raw_data.user.id) || project.creator_id + gitlab_author_id || project.creator_id end def body @@ -85,7 +85,11 @@ module Gitlab end def description - formatter.author_line(author) + body + if gitlab_author_id + body + else + formatter.author_line(author) + body + end end def milestone diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb index 2c0a2dd94ca..2b4670be468 100644 --- a/spec/factories/issues.rb +++ b/spec/factories/issues.rb @@ -1,4 +1,8 @@ FactoryGirl.define do + sequence :issue_created_at do |n| + 4.hours.ago + ( 2 * n ).seconds + end + factory :issue do title author diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb index 9ae02a6c45f..c520a9c53ad 100644 --- a/spec/lib/gitlab/github_import/comment_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb @@ -73,6 +73,12 @@ describe Gitlab::GithubImport::CommentFormatter, lib: true do gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github') expect(comment.attributes.fetch(:author_id)).to eq gl_user.id end + + it 'returns note without created at tag line' do + create(:omniauth_user, extern_uid: octocat.id, provider: 'github') + + expect(comment.attributes.fetch(:note)).to eq("I'm having a problem with this.") + end end end end diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb index d60c4111e99..c2f1f6b91a1 100644 --- a/spec/lib/gitlab/github_import/issue_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb @@ -109,6 +109,12 @@ describe Gitlab::GithubImport::IssueFormatter, lib: true do expect(issue.attributes.fetch(:author_id)).to eq gl_user.id end + + it 'returns description without created at tag line' do + create(:omniauth_user, extern_uid: octocat.id, provider: 'github') + + expect(issue.attributes.fetch(:description)).to eq("I'm having a problem with this.") + end end end diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb index edfc6ad81c6..302f0fc0623 100644 --- a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb +++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb @@ -140,6 +140,12 @@ describe Gitlab::GithubImport::PullRequestFormatter, lib: true do expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id end + + it 'returns description without created at tag line' do + create(:omniauth_user, extern_uid: octocat.id, provider: 'github') + + expect(pull_request.attributes.fetch(:description)).to eq('Please pull these awesome changes') + end end context 'when it has a milestone' do diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index c45c2635cf4..8eab4281bc7 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -231,6 +231,34 @@ describe Ci::Build, models: true do it { is_expected.to eq(predefined_variables) } end + context 'when build has user' do + let(:user) { create(:user, username: 'starter') } + let(:user_variables) do + [ + { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true }, + { key: 'GITLAB_USER_EMAIL', value: user.email, public: true } + ] + end + + before do + build.update_attributes(user: user) + end + + it { user_variables.each { |v| is_expected.to include(v) } } + end + + context 'when build started manually' do + before do + build.update_attributes(when: :manual) + end + + let(:manual_variable) do + { key: 'CI_BUILD_MANUAL', value: 'true', public: true } + end + + it { is_expected.to include(manual_variable) } + end + context 'when build is for tag' do let(:tag_variable) do { key: 'CI_BUILD_TAG', value: 'master', public: true } diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb index 2d6093fec7a..7aa7e85a9e2 100644 --- a/spec/requests/api/commit_statuses_spec.rb +++ b/spec/requests/api/commit_statuses_spec.rb @@ -117,17 +117,36 @@ describe API::CommitStatuses, api: true do let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" } context 'developer user' do - context 'only required parameters' do - before { post api(post_url, developer), state: 'success' } + %w[pending running success failed canceled].each do |status| + context "for #{status}" do + context 'uses only required parameters' do + it 'creates commit status' do + post api(post_url, developer), state: status + + expect(response).to have_http_status(201) + expect(json_response['sha']).to eq(commit.id) + expect(json_response['status']).to eq(status) + expect(json_response['name']).to eq('default') + expect(json_response['ref']).not_to be_empty + expect(json_response['target_url']).to be_nil + expect(json_response['description']).to be_nil + end + end + end + end - it 'creates commit status' do - expect(response).to have_http_status(201) - expect(json_response['sha']).to eq(commit.id) - expect(json_response['status']).to eq('success') - expect(json_response['name']).to eq('default') - expect(json_response['ref']).to be_nil - expect(json_response['target_url']).to be_nil - expect(json_response['description']).to be_nil + context 'transitions status from pending' do + before do + post api(post_url, developer), state: 'pending' + end + + %w[running success failed canceled].each do |status| + it "to #{status}" do + expect { post api(post_url, developer), state: status }.not_to change { CommitStatus.count } + + expect(response).to have_http_status(201) + expect(json_response['status']).to eq(status) + end end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 47344a13b5e..f840778ae9b 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -17,21 +17,27 @@ describe API::API, api: true do assignee: user, project: project, state: :closed, - milestone: milestone + milestone: milestone, + created_at: generate(:issue_created_at), + updated_at: 3.hours.ago end let!(:confidential_issue) do create :issue, :confidential, project: project, author: author, - assignee: assignee + assignee: assignee, + created_at: generate(:issue_created_at), + updated_at: 2.hours.ago end let!(:issue) do create :issue, author: user, assignee: user, project: project, - milestone: milestone + milestone: milestone, + created_at: generate(:issue_created_at), + updated_at: 1.hour.ago end let!(:label) do create(:label, title: 'label', color: '#FFAABB', project: project) @@ -135,6 +141,42 @@ describe API::API, api: true do expect(json_response).to be_an Array expect(json_response.length).to eq(0) end + + it 'sorts by created_at descending by default' do + get api('/issues', user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts ascending when requested' do + get api('/issues?sort=asc', user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at descending when requested' do + get api('/issues?order_by=updated_at', user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at ascending when requested' do + get api('/issues?order_by=updated_at&sort=asc', user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end end end @@ -147,21 +189,24 @@ describe API::API, api: true do assignee: user, project: group_project, state: :closed, - milestone: group_milestone + milestone: group_milestone, + updated_at: 3.hours.ago end let!(:group_confidential_issue) do create :issue, :confidential, project: group_project, author: author, - assignee: assignee + assignee: assignee, + updated_at: 2.hours.ago end let!(:group_issue) do create :issue, author: user, assignee: user, project: group_project, - milestone: group_milestone + milestone: group_milestone, + updated_at: 1.hour.ago end let!(:group_label) do create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project) @@ -278,6 +323,42 @@ describe API::API, api: true do expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(group_closed_issue.id) end + + it 'sorts by created_at descending by default' do + get api(base_url, user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts ascending when requested' do + get api("#{base_url}?sort=asc", user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at descending when requested' do + get api("#{base_url}?order_by=updated_at", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at ascending when requested' do + get api("#{base_url}?order_by=updated_at&sort=asc", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end end describe "GET /projects/:id/issues" do @@ -386,6 +467,42 @@ describe API::API, api: true do expect(json_response.length).to eq(1) expect(json_response.first['id']).to eq(closed_issue.id) end + + it 'sorts by created_at descending by default' do + get api("#{base_url}/issues", user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts ascending when requested' do + get api("#{base_url}/issues?sort=asc", user) + response_dates = json_response.map { |issue| issue['created_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end + + it 'sorts by updated_at descending when requested' do + get api("#{base_url}/issues?order_by=updated_at", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort.reverse) + end + + it 'sorts by updated_at ascending when requested' do + get api("#{base_url}/issues?order_by=updated_at&sort=asc", user) + response_dates = json_response.map { |issue| issue['updated_at'] } + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(response_dates).to eq(response_dates.sort) + end end describe "GET /projects/:id/issues/:issue_id" do diff --git a/spec/requests/api/notification_settings_spec.rb b/spec/requests/api/notification_settings_spec.rb new file mode 100644 index 00000000000..e6d8a5ee954 --- /dev/null +++ b/spec/requests/api/notification_settings_spec.rb @@ -0,0 +1,89 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:user) { create(:user) } + let!(:group) { create(:group) } + let!(:project) { create(:project, :public, creator_id: user.id, namespace: group) } + + describe "GET /notification_settings" do + it "returns global notification settings for the current user" do + get api("/notification_settings", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_a Hash + expect(json_response['notification_email']).to eq(user.notification_email) + expect(json_response['level']).to eq(user.global_notification_setting.level) + end + end + + describe "PUT /notification_settings" do + let(:email) { create(:email, user: user) } + + it "updates global notification settings for the current user" do + put api("/notification_settings", user), { level: 'watch', notification_email: email.email } + + expect(response).to have_http_status(200) + expect(json_response['notification_email']).to eq(email.email) + expect(user.reload.notification_email).to eq(email.email) + expect(json_response['level']).to eq(user.reload.global_notification_setting.level) + end + end + + describe "PUT /notification_settings" do + it "fails on non-user email address" do + put api("/notification_settings", user), { notification_email: 'invalid@example.com' } + + expect(response).to have_http_status(400) + end + end + + describe "GET /groups/:id/notification_settings" do + it "returns group level notification settings for the current user" do + get api("/groups/#{group.id}/notification_settings", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_a Hash + expect(json_response['level']).to eq(user.notification_settings_for(group).level) + end + end + + describe "PUT /groups/:id/notification_settings" do + it "updates group level notification settings for the current user" do + put api("/groups/#{group.id}/notification_settings", user), { level: 'watch' } + + expect(response).to have_http_status(200) + expect(json_response['level']).to eq(user.reload.notification_settings_for(group).level) + end + end + + describe "GET /projects/:id/notification_settings" do + it "returns project level notification settings for the current user" do + get api("/projects/#{project.id}/notification_settings", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_a Hash + expect(json_response['level']).to eq(user.notification_settings_for(project).level) + end + end + + describe "PUT /projects/:id/notification_settings" do + it "updates project level notification settings for the current user" do + put api("/projects/#{project.id}/notification_settings", user), { level: 'custom', new_note: true } + + expect(response).to have_http_status(200) + expect(json_response['level']).to eq(user.reload.notification_settings_for(project).level) + expect(json_response['events']['new_note']).to eq(true) + expect(json_response['events']['new_issue']).to eq(false) + end + end + + describe "PUT /projects/:id/notification_settings" do + it "fails on invalid level" do + put api("/projects/#{project.id}/notification_settings", user), { level: 'invalid' } + + expect(response).to have_http_status(400) + end + end +end diff --git a/vendor/gitignore/Global/NetBeans.gitignore b/vendor/gitignore/Global/NetBeans.gitignore index 520d91ff584..254108cd23b 100644 --- a/vendor/gitignore/Global/NetBeans.gitignore +++ b/vendor/gitignore/Global/NetBeans.gitignore @@ -3,5 +3,4 @@ build/ nbbuild/ dist/ nbdist/ -nbactions.xml .nb-gradle/ diff --git a/vendor/gitignore/Global/Tags.gitignore b/vendor/gitignore/Global/Tags.gitignore index c0318165a27..91927af4cd6 100644 --- a/vendor/gitignore/Global/Tags.gitignore +++ b/vendor/gitignore/Global/Tags.gitignore @@ -9,6 +9,7 @@ gtags.files GTAGS GRTAGS GPATH +GSYMS cscope.files cscope.out cscope.in.out diff --git a/vendor/gitignore/Global/OSX.gitignore b/vendor/gitignore/Global/macOS.gitignore index 5972fe50f66..828a509a137 100644 --- a/vendor/gitignore/Global/OSX.gitignore +++ b/vendor/gitignore/Global/macOS.gitignore @@ -3,7 +3,8 @@ .LSOverride # Icon must end with two \r -Icon
+Icon + # Thumbnails ._* diff --git a/vendor/gitignore/Haskell.gitignore b/vendor/gitignore/Haskell.gitignore index a4ee41ab62b..450f32ec40c 100644 --- a/vendor/gitignore/Haskell.gitignore +++ b/vendor/gitignore/Haskell.gitignore @@ -17,3 +17,4 @@ cabal.sandbox.config *.eventlog .stack-work/ cabal.project.local +.HTF/ diff --git a/vendor/gitignore/Joomla.gitignore b/vendor/gitignore/Joomla.gitignore index 0d7a0de298f..93103fdbe77 100644 --- a/vendor/gitignore/Joomla.gitignore +++ b/vendor/gitignore/Joomla.gitignore @@ -52,6 +52,7 @@ /administrator/language/en-GB/en-GB.plg_content_contact.sys.ini /administrator/language/en-GB/en-GB.plg_content_finder.ini /administrator/language/en-GB/en-GB.plg_content_finder.sys.ini +/administrator/language/en-GB/en-GB.plg_editors-xtd_module* /administrator/language/en-GB/en-GB.plg_finder_categories.ini /administrator/language/en-GB/en-GB.plg_finder_categories.sys.ini /administrator/language/en-GB/en-GB.plg_finder_contacts.ini @@ -64,6 +65,10 @@ /administrator/language/en-GB/en-GB.plg_finder_tags.sys.ini /administrator/language/en-GB/en-GB.plg_finder_weblinks.ini /administrator/language/en-GB/en-GB.plg_finder_weblinks.sys.ini +/administrator/language/en-GB/en-GB.plg_installer_folderinstaller* +/administrator/language/en-GB/en-GB.plg_installer_packageinstaller* +/administrator/language/en-GB/en-GB.plg_installer_packageinstaller +/administrator/language/en-GB/en-GB.plg_installer_urlinstaller* /administrator/language/en-GB/en-GB.plg_installer_webinstaller.ini /administrator/language/en-GB/en-GB.plg_installer_webinstaller.sys.ini /administrator/language/en-GB/en-GB.plg_quickicon_joomlaupdate.ini @@ -72,6 +77,8 @@ /administrator/language/en-GB/en-GB.plg_search_tags.sys.ini /administrator/language/en-GB/en-GB.plg_system_languagecode.ini /administrator/language/en-GB/en-GB.plg_system_languagecode.sys.ini +/administrator/language/en-GB/en-GB.plg_system_stats* +/administrator/language/en-GB/en-GB.plg_system_updatenotification* /administrator/language/en-GB/en-GB.plg_twofactorauth_totp.ini /administrator/language/en-GB/en-GB.plg_twofactorauth_totp.sys.ini /administrator/language/en-GB/en-GB.plg_twofactorauth_yubikey.ini @@ -249,8 +256,10 @@ /administrator/language/en-GB/en-GB.tpl_hathor.sys.ini /administrator/language/en-GB/en-GB.xml /administrator/language/en-GB/index.html +/administrator/language/ru-RU/index.html /administrator/language/overrides/* /administrator/language/index.html +/administrator/logs/index.html /administrator/manifests/* /administrator/modules/mod_custom/* /administrator/modules/mod_feed/* @@ -289,6 +298,7 @@ /components/com_finder/* /components/com_mailto/* /components/com_media/* +/components/com_modules/* /components/com_newsfeeds/* /components/com_search/* /components/com_users/* @@ -407,6 +417,7 @@ /libraries/idna_convert/* /libraries/joomla/* /libraries/legacy/* +/libraries/php-encryption/* /libraries/phpass/* /libraries/phpmailer/* /libraries/phputf8/* @@ -431,9 +442,11 @@ /media/media/* /media/mod_languages/* /media/overrider/* +/media/plg_captcha_recaptcha/* /media/plg_quickicon_extensionupdate/* /media/plg_quickicon_joomlaupdate/* /media/plg_system_highlight/* +/media/plg_system_stats/* /media/system/* /media/index.html /modules/mod_articles_archive/* @@ -486,6 +499,7 @@ /plugins/editors/none/* /plugins/editors/tinymce/* /plugins/editors/index.html +/plugins/editors-xtd/module/* /plugins/editors-xtd/article/* /plugins/editors-xtd/image/* /plugins/editors-xtd/pagebreak/* @@ -523,6 +537,8 @@ /plugins/system/redirect/* /plugins/system/remember/* /plugins/system/sef/* +/plugins/system/stats/* +/plugins/system/updatenotification/* /plugins/system/index.html /plugins/twofactorauth/* /plugins/user/contactcreator/* diff --git a/vendor/gitignore/Node.gitignore b/vendor/gitignore/Node.gitignore index aea5294de9d..bf7525f9912 100644 --- a/vendor/gitignore/Node.gitignore +++ b/vendor/gitignore/Node.gitignore @@ -34,5 +34,8 @@ jspm_packages # Optional npm cache directory .npm +# Optional eslint cache +.eslintcache + # Optional REPL history .node_repl_history diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore index 20592083931..58c51ecaed4 100644 --- a/vendor/gitignore/Objective-C.gitignore +++ b/vendor/gitignore/Objective-C.gitignore @@ -50,7 +50,9 @@ Carthage/Build # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md fastlane/report.xml +fastlane/Preview.html fastlane/screenshots +fastlane/test_output # Code Injection # diff --git a/vendor/gitignore/Python.gitignore b/vendor/gitignore/Python.gitignore index 72364f99fe4..37fc9d40817 100644 --- a/vendor/gitignore/Python.gitignore +++ b/vendor/gitignore/Python.gitignore @@ -79,6 +79,7 @@ celerybeat-schedule .env # virtualenv +.venv/ venv/ ENV/ diff --git a/vendor/gitignore/Rails.gitignore b/vendor/gitignore/Rails.gitignore index d8c256c1925..e97427608c1 100644 --- a/vendor/gitignore/Rails.gitignore +++ b/vendor/gitignore/Rails.gitignore @@ -12,9 +12,11 @@ capybara-*.html rerun.txt pickle-email-*.html -# TODO Comment out these rules if you are OK with secrets being uploaded to the repo +# TODO Comment out this rule if you are OK with secrets being uploaded to the repo config/initializers/secret_token.rb -config/secrets.yml + +# Only include if you have production secrets in this file, which is no longer a Rails default +# config/secrets.yml # dotenv # TODO Comment out this rule if environment variables can be committed diff --git a/vendor/gitignore/VisualStudio.gitignore b/vendor/gitignore/VisualStudio.gitignore index 67acbf42f5e..d56f8b53288 100644 --- a/vendor/gitignore/VisualStudio.gitignore +++ b/vendor/gitignore/VisualStudio.gitignore @@ -251,3 +251,10 @@ paket-files/ # JetBrains Rider .idea/ *.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc diff --git a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml index 396d3f1b042..f3fa3949656 100644 --- a/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Docker.gitlab-ci.yml @@ -1,7 +1,12 @@ # Official docker image. image: docker:latest +services: + - docker:dind + build: stage: build script: - - docker build -t test . + - docker login -u "gitlab-ci-token" -p "$CI_BUILD_TOKEN" $CI_REGISTRY + - docker build --pull -t "$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME" . + - docker push "$CI_REGISTRY_IMAGE:$CI_BUILD_REF_NAME" diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml index 166f146ee05..08b57c8c0ac 100644 --- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml @@ -43,3 +43,12 @@ rails: - bundle exec rake db:migrate - bundle exec rake db:seed - bundle exec rake test + +# This deploy job uses a simple deploy flow to Heroku, other providers, e.g. AWS Elastic Beanstalk +# are supported too: https://github.com/travis-ci/dpl +deploy: + type: deploy + environment: production + script: + - gem install dpl + - dpl --provider=heroku --app=$HEROKU_APP_NAME --api-key=$HEROKU_PRODUCTION_KEY diff --git a/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml new file mode 100644 index 00000000000..c9c35906d1c --- /dev/null +++ b/vendor/gitlab-ci-yml/Swift.gitlab-ci.yml @@ -0,0 +1,30 @@ +# Lifted from: https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/ +# This file assumes an own GitLab CI runner, setup on an OS X system. +stages: + - build + - archive + +build_project: + stage: build + script: + - xcodebuild clean -project ProjectName.xcodeproj -scheme SchemeName | xcpretty + - xcodebuild test -project ProjectName.xcodeproj -scheme SchemeName -destination 'platform=iOS Simulator,name=iPhone 6s,OS=9.2' | xcpretty -s + tags: + - ios_9-2 + - xcode_7-2 + - osx_10-11 + +archive_project: + stage: archive + script: + - xcodebuild clean archive -archivePath build/ProjectName -scheme SchemeName + - xcodebuild -exportArchive -exportFormat ipa -archivePath "build/ProjectName.xcarchive" -exportPath "build/ProjectName.ipa" -exportProvisioningProfile "ProvisioningProfileName" + only: + - master + artifacts: + paths: + - build/ProjectName.ipa + tags: + - ios_9-2 + - xcode_7-2 + - osx_10-11 |