diff options
66 files changed, 946 insertions, 254 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2c3d18d21be..3d12f4142ba 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -231,10 +231,16 @@ package-and-qa: <<: *single-script-job variables: <<: *single-script-job-variables + API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}" SCRIPT_NAME: trigger-build retry: 0 script: - gem install gitlab --no-document + - apk add --update openssl curl jq + - wget $CI_PROJECT_URL/raw/$CI_COMMIT_SHA/scripts/review_apps/review-apps.sh + - chmod 755 review-apps.sh + - source ./review-apps.sh + - wait_for_job_to_be_done "gitlab:assets:compile" - ./$SCRIPT_NAME omnibus when: manual only: @@ -600,7 +606,7 @@ gitlab:setup-mysql: # Frontend-related jobs gitlab:assets:compile: - <<: *dedicated-no-docs-and-no-qa-pull-cache-job + <<: *dedicated-no-docs-pull-cache-job image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-69.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1 dependencies: [] services: @@ -89,8 +89,9 @@ gem 'kaminari', '~> 1.0' gem 'hamlit', '~> 2.8.8' # Files attachments -# Locked until https://github.com/carrierwaveuploader/carrierwave/pull/2332/files is merged. -# config/initializers/carrierwave_patch.rb can be removed once that change is released. +# Locked until https://github.com/carrierwaveuploader/carrierwave/pull/2332 and +# https://github.com/carrierwaveuploader/carrierwave/pull/2356 are merged. +# config/initializers/carrierwave_patch.rb can be removed once both changes are released. gem 'carrierwave', '= 1.2.3' gem 'mini_magick' @@ -164,6 +165,7 @@ gem 'acts-as-taggable-on', '~> 5.0' gem 'sidekiq', '~> 5.2.1' gem 'sidekiq-cron', '~> 0.6.0' gem 'redis-namespace', '~> 1.6.0' +gem 'gitlab-sidekiq-fetcher', '~> 0.1.0', require: 'sidekiq-reliable-fetch' # Cron Parser gem 'rufus-scheduler', '~> 3.4' @@ -294,7 +296,6 @@ gem 'peek-mysql2', '~> 1.2.0', group: :mysql gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-redis', '~> 1.2.0' -gem 'gitlab-sidekiq-fetcher', require: 'sidekiq-reliable-fetch' # Metrics group :metrics do diff --git a/Gemfile.lock b/Gemfile.lock index 312ad95cd1e..6d21089c82c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -280,7 +280,7 @@ GEM gitlab-default_value_for (3.1.1) activerecord (>= 3.2.0, < 6.0) gitlab-markup (1.6.5) - gitlab-sidekiq-fetcher (0.3.0) + gitlab-sidekiq-fetcher (0.1.0) sidekiq (~> 5) gitlab-styles (2.4.1) rubocop (~> 0.54.0) @@ -1012,7 +1012,7 @@ DEPENDENCIES github-markup (~> 1.7.0) gitlab-default_value_for (~> 3.1.1) gitlab-markup (~> 1.6.5) - gitlab-sidekiq-fetcher + gitlab-sidekiq-fetcher (~> 0.1.0) gitlab-styles (~> 2.4) gitlab_omniauth-ldap (~> 2.0.4) gon (~> 6.2) diff --git a/app/assets/images/none-scheme-preview.png b/app/assets/images/none-scheme-preview.png Binary files differnew file mode 100644 index 00000000000..2eb6bf96671 --- /dev/null +++ b/app/assets/images/none-scheme-preview.png diff --git a/app/assets/javascripts/monitoring/utils/multiple_time_series.js b/app/assets/javascripts/monitoring/utils/multiple_time_series.js index bb24a1acdb3..50ba14dfb2e 100644 --- a/app/assets/javascripts/monitoring/utils/multiple_time_series.js +++ b/app/assets/javascripts/monitoring/utils/multiple_time_series.js @@ -92,7 +92,11 @@ function queryTimeSeries(query, graphDrawData, lineStyle) { if (seriesCustomizationData) { metricTag = seriesCustomizationData.value || timeSeriesMetricLabel; [lineColor, areaColor] = pickColor(seriesCustomizationData.color); - shouldRenderLegend = false; + if (timeSeriesParsed.length > 0) { + shouldRenderLegend = false; + } else { + shouldRenderLegend = true; + } } else { metricTag = timeSeriesMetricLabel || query.label || `series ${timeSeriesNumber + 1}`; [lineColor, areaColor] = pickColor(); @@ -101,19 +105,6 @@ function queryTimeSeries(query, graphDrawData, lineStyle) { } } - if (!shouldRenderLegend) { - if (!timeSeriesParsed[0].tracksLegend) { - timeSeriesParsed[0].tracksLegend = []; - } - timeSeriesParsed[0].tracksLegend.push({ - max: maximumValue, - average: accum / timeSeries.values.length, - lineStyle, - lineColor, - metricTag, - }); - } - const values = datesWithoutGaps.map(time => ({ time, value: findByDate(timeSeries.values, time), @@ -135,6 +126,19 @@ function queryTimeSeries(query, graphDrawData, lineStyle) { shouldRenderLegend, renderCanary, }); + + if (!shouldRenderLegend) { + if (!timeSeriesParsed[0].tracksLegend) { + timeSeriesParsed[0].tracksLegend = []; + } + timeSeriesParsed[0].tracksLegend.push({ + max: maximumValue, + average: accum / timeSeries.values.length, + lineStyle, + lineColor, + metricTag, + }); + } }); return timeSeriesParsed; diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue index 07c938a0021..d9dd08a7a6b 100644 --- a/app/assets/javascripts/notes/components/noteable_discussion.vue +++ b/app/assets/javascripts/notes/components/noteable_discussion.vue @@ -378,6 +378,14 @@ Please check your network connection and try again.`; :help-page-path="helpPagePath" @handleDeleteNote="deleteNoteHandler" > + <note-edited-text + v-if="discussion.resolved" + slot="discussion-resolved-text" + :edited-at="discussion.resolved_at" + :edited-by="discussion.resolved_by" + :action-text="resolvedText" + class-name="discussion-headline-light js-discussion-headline discussion-resolved-text" + /> <slot slot="avatar-badge" name="avatar-badge"></slot> </component> <toggle-replies-widget diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 57e9c40bd61..4c02588127e 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -195,7 +195,7 @@ export default { :img-alt="author.name" :img-size="40" > - <slot slot="avatar-badge" name="avatar-badge"> </slot> + <slot slot="avatar-badge" name="avatar-badge"></slot> </user-avatar-link> </div> <div class="timeline-content"> @@ -227,16 +227,19 @@ export default { @handleResolve="resolveHandler" /> </div> - <note-body - ref="noteBody" - :note="note" - :line="line" - :can-edit="note.current_user.can_edit" - :is-editing="isEditing" - :help-page-path="helpPagePath" - @handleFormUpdate="formUpdateHandler" - @cancelForm="formCancelHandler" - /> + <div class="timeline-discussion-body"> + <slot name="discussion-resolved-text"></slot> + <note-body + ref="noteBody" + :note="note" + :line="line" + :can-edit="note.current_user.can_edit" + :is-editing="isEditing" + :help-page-path="helpPagePath" + @handleFormUpdate="formUpdateHandler" + @cancelForm="formCancelHandler" + /> + </div> </div> </timeline-entry-item> </template> diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 985fac11c87..bdf20866197 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -47,6 +47,7 @@ @import "highlight/solarized_dark"; @import "highlight/solarized_light"; @import "highlight/white"; +@import "highlight/none"; /* * Styles for JS behaviors. diff --git a/app/assets/stylesheets/highlight/none.scss b/app/assets/stylesheets/highlight/none.scss new file mode 100644 index 00000000000..7d692a87e33 --- /dev/null +++ b/app/assets/stylesheets/highlight/none.scss @@ -0,0 +1,242 @@ +/* +* None Syntax Colors +*/ + + + +@mixin matchLine { + color: $black-transparent; + background-color: $white-normal; +} + +.code.none { + // Line numbers + .line-numbers, + .diff-line-num { + background-color: $gray-light; + } + + .diff-line-num, + .diff-line-num a { + color: $black-transparent; + } + + // Code itself + pre.code, + .diff-line-num { + border-color: $white-normal; + } + + &, + pre.code, + .line_holder .line_content { + background-color: $white-light; + color: $gl-text-color; + } + +// Diff line + + $none-over-bg: #ded7fc; + $none-expanded-border: #e0e0e0; + $none-expanded-bg: #f7f7f7; + + .line_holder { + + &.match .line_content, + .new-nonewline.line_content, + .old-nonewline.line_content { + @include matchLine; + } + + .diff-line-num { + &.old { + background-color: $line-number-old; + border-color: $line-removed-dark; + + a { + color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%); + } + } + + &.new { + background-color: $line-number-new; + border-color: $line-added-dark; + + a { + color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%); + } + } + + &.is-over, + &.hll:not(.empty-cell).is-over { + background-color: $none-over-bg; + border-color: darken($none-over-bg, 5%); + + a { + color: darken($none-over-bg, 15%); + } + } + + &.hll:not(.empty-cell) { + background-color: $line-number-select; + border-color: $line-select-yellow-dark; + } + } + + &:not(.diff-expanded) + .diff-expanded, + &.diff-expanded + .line_holder:not(.diff-expanded) { + > .diff-line-num, + > .line_content { + border-top: 1px solid $none-expanded-border; + } + } + + &.diff-expanded { + > .diff-line-num, + > .line_content { + background: $none-expanded-bg; + border-color: $none-expanded-bg; + } + } + + .line_content { + &.old { + background-color: $line-removed; + + &::before { + color: scale-color($line-number-old, $red: -30%, $green: -30%, $blue: -30%); + } + + span.idiff { + background-color: $line-removed-dark; + } + } + + &.new { + background-color: $line-added; + + &::before { + color: scale-color($line-number-new, $red: -30%, $green: -30%, $blue: -30%); + } + + span.idiff { + background-color: $line-added-dark; + } + } + + &.match { + @include matchLine; + } + + &.hll:not(.empty-cell) { + background-color: $line-select-yellow; + } + } + } + + // highlight line via anchor + pre .hll { + background-color: $white-normal; + } + + // Search result highlight + span.highlight_word { + background-color: $white-normal; + } + + // Links to URLs, emails, or dependencies + .line a { + color: $gl-text-color; + text-decoration: underline; + } + + .hll { background-color: $white-light; } + + .gd { + color: $gl-text-color; + background-color: $white-light; + + .x { + color: $gl-text-color; + background-color: $white-light; + } + } + + .gi { + color: $gl-text-color; + background-color: $white-light; + + .x { + color: $gl-text-color; + background-color: $white-light; + } + } + + .c { color: $gl-text-color; } /* Comment */ + .err { color: $gl-text-color; } /* Error */ + .g { color: $gl-text-color; } /* Generic */ + .k { color: $gl-text-color; } /* Keyword */ + .l { color: $gl-text-color; } /* Literal */ + .n { color: $gl-text-color; } /* Name */ + .o { color: $gl-text-color; } /* Operator */ + .x { color: $gl-text-color; } /* Other */ + .p { color: $gl-text-color; } /* Punctuation */ + .cm { color: $gl-text-color; } /* Comment.Multiline */ + .cp { color: $gl-text-color; } /* Comment.Preproc */ + .c1 { color: $gl-text-color; } /* Comment.Single */ + .cs { color: $gl-text-color; } /* Comment.Special */ + .ge { color: $gl-text-color; } /* Generic.Emph */ + .gr { color: $gl-text-color; } /* Generic.Error */ + .gh { color: $gl-text-color; } /* Generic.Heading */ + .go { color: $gl-text-color; } /* Generic.Output */ + .gp { color: $gl-text-color; } /* Generic.Prompt */ + .gs { color: $gl-text-color; } /* Generic.Strong */ + .gu { color: $gl-text-color; } /* Generic.Subheading */ + .gt { color: $gl-text-color; } /* Generic.Traceback */ + .kc { color: $gl-text-color; } /* Keyword.Constant */ + .kd { color: $gl-text-color; } /* Keyword.Declaration */ + .kn { color: $gl-text-color; } /* Keyword.Namespace */ + .kp { color: $gl-text-color; } /* Keyword.Pseudo */ + .kr { color: $gl-text-color; } /* Keyword.Reserved */ + .kt { color: $gl-text-color; } /* Keyword.Type */ + .ld { color: $gl-text-color; } /* Literal.Date */ + .m { color: $gl-text-color; } /* Literal.Number */ + .s { color: $gl-text-color; } /* Literal.String */ + .na { color: $gl-text-color; } /* Name.Attribute */ + .nb { color: $gl-text-color; } /* Name.Builtin */ + .nc { color: $gl-text-color; } /* Name.Class */ + .no { color: $gl-text-color; } /* Name.Constant */ + .nd { color: $gl-text-color; } /* Name.Decorator */ + .ni { color: $gl-text-color; } /* Name.Entity */ + .ne { color: $gl-text-color; } /* Name.Exception */ + .nf { color: $gl-text-color; } /* Name.Function */ + .nl { color: $gl-text-color; } /* Name.Label */ + .nn { color: $gl-text-color; } /* Name.Namespace */ + .nx { color: $gl-text-color; } /* Name.Other */ + .py { color: $gl-text-color; } /* Name.Property */ + .nt { color: $gl-text-color; } /* Name.Tag */ + .nv { color: $gl-text-color; } /* Name.Variable */ + .ow { color: $gl-text-color; } /* Operator.Word */ + .w { color: $gl-text-color; } /* Text.Whitespace */ + .mf { color: $gl-text-color; } /* Literal.Number.Float */ + .mh { color: $gl-text-color; } /* Literal.Number.Hex */ + .mi { color: $gl-text-color; } /* Literal.Number.Integer */ + .mo { color: $gl-text-color; } /* Literal.Number.Oct */ + .sb { color: $gl-text-color; } /* Literal.String.Backtick */ + .sc { color: $gl-text-color; } /* Literal.String.Char */ + .sd { color: $gl-text-color; } /* Literal.String.Doc */ + .s2 { color: $gl-text-color; } /* Literal.String.Double */ + .se { color: $gl-text-color; } /* Literal.String.Escape */ + .sh { color: $gl-text-color; } /* Literal.String.Heredoc */ + .si { color: $gl-text-color; } /* Literal.String.Interpol */ + .sx { color: $gl-text-color; } /* Literal.String.Other */ + .sr { color: $gl-text-color; } /* Literal.String.Regex */ + .s1 { color: $gl-text-color; } /* Literal.String.Single */ + .ss { color: $gl-text-color; } /* Literal.String.Symbol */ + .bp { color: $gl-text-color; } /* Name.Builtin.Pseudo */ + .vc { color: $gl-text-color; } /* Name.Variable.Class */ + .vg { color: $gl-text-color; } /* Name.Variable.Global */ + .vi { color: $gl-text-color; } /* Name.Variable.Instance */ + .il { color: $gl-text-color; } /* Literal.Number.Integer.Long */ + +} diff --git a/app/assets/stylesheets/pages/merge_conflicts.scss b/app/assets/stylesheets/pages/merge_conflicts.scss index d26659701e1..e0f7d075fc7 100644 --- a/app/assets/stylesheets/pages/merge_conflicts.scss +++ b/app/assets/stylesheets/pages/merge_conflicts.scss @@ -93,8 +93,28 @@ $colors: ( solarized-dark-line-origin-chosen : rgba(#2878c9, .35), solarized-dark-button-origin-chosen : #0082cc, - solarized-dark-header-not-chosen : rgba(#839496, .25), - solarized-dark-line-not-chosen : rgba(#839496, .15) + solarized_dark_header_not_chosen : rgba(#839496, .25), + solarized_dark_line_not_chosen : rgba(#839496, .15), + + none_header_head_neutral : $gray-normal, + none_line_head_neutral : $gray-normal, + none_button_head_neutral : $gray-normal, + + none_header_head_chosen : $gray-darker, + none_line_head_chosen : $gray-darker, + none_button_head_chosen : $gray-darker, + + none_header_origin_neutral : $gray-normal, + none_line_origin_neutral : $gray-normal, + none_button_origin_neutral : $gray-normal, + + none_header_origin_chosen : $gray-darker, + none_line_origin_chosen : $gray-darker, + none_button_origin_chosen : $gray-darker, + + none_header_not_chosen : $gray-light, + none_line_not_chosen : $gray-light + ); // scss-lint:enable ColorVariable diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 2adfa0d312e..a5b1eff3e1d 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -152,6 +152,16 @@ $note-form-margin-left: 72px; display: block; position: relative; + .timeline-discussion-body { + margin-top: -8px; + overflow-x: auto; + overflow-y: hidden; + + .discussion-resolved-text { + margin-bottom: 8px; + } + } + .diff-content { overflow: visible; padding: 0; diff --git a/app/controllers/concerns/service_params.rb b/app/controllers/concerns/service_params.rb index 8bd93a349ef..c6ae4fe15bf 100644 --- a/app/controllers/concerns/service_params.rb +++ b/app/controllers/concerns/service_params.rb @@ -70,7 +70,7 @@ module ServiceParams def service_params dynamic_params = @service.event_channel_names + @service.event_names # rubocop:disable Gitlab/ModuleWithInstanceVariables - service_params = params.permit(:id, service: ALLOWED_PARAMS_CE + dynamic_params) + service_params = params.permit(:id, service: allowed_service_params + dynamic_params) if service_params[:service].is_a?(Hash) FILTER_BLANK_PARAMS.each do |param| @@ -80,4 +80,8 @@ module ServiceParams service_params end + + def allowed_service_params + ALLOWED_PARAMS_CE + end end diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index c58b30eace7..bfbbcba883f 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -9,7 +9,7 @@ class Projects::JobsController < Projects::ApplicationController before_action :authorize_update_build!, except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase] before_action :authorize_erase_build!, only: [:erase] - before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_workhorse_authorize] + before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize] before_action :verify_api_request!, only: :terminal_websocket_authorize layout 'project' diff --git a/app/finders/concerns/finder_with_cross_project_access.rb b/app/finders/concerns/finder_with_cross_project_access.rb index 220f62bcc7f..06ebb286086 100644 --- a/app/finders/concerns/finder_with_cross_project_access.rb +++ b/app/finders/concerns/finder_with_cross_project_access.rb @@ -5,7 +5,8 @@ # # This module depends on the finder implementing the following methods: # -# - `#execute` should return an `ActiveRecord::Relation` +# - `#execute` should return an `ActiveRecord::Relation` or the `model` needs to +# be defined in the call to `requires_cross_project_access`. # - `#current_user` the user that requires access (or nil) module FinderWithCrossProjectAccess extend ActiveSupport::Concern @@ -13,20 +14,35 @@ module FinderWithCrossProjectAccess prepended do extend Gitlab::CrossProjectAccess::ClassMethods + + cattr_accessor :finder_model + + def self.requires_cross_project_access(*args) + super + + self.finder_model = extract_model_from_arguments(args) + end + + private + + def self.extract_model_from_arguments(args) + args.detect { |argument| argument.is_a?(Hash) && argument[:model] } + &.fetch(:model) + end end override :execute def execute(*args) check = Gitlab::CrossProjectAccess.find_check(self) - original = super + original = -> { super } - return original unless check - return original if should_skip_cross_project_check || can_read_cross_project? + return original.call unless check + return original.call if should_skip_cross_project_check || can_read_cross_project? if check.should_run?(self) - original.model.none + finder_model&.none || original.call.model.none else - original + original.call end end @@ -48,8 +64,6 @@ module FinderWithCrossProjectAccess skip_cross_project_check { super } end - private - attr_accessor :should_skip_cross_project_check def skip_cross_project_check diff --git a/app/finders/events_finder.rb b/app/finders/events_finder.rb index 8df01f1dad9..234b7090fd9 100644 --- a/app/finders/events_finder.rb +++ b/app/finders/events_finder.rb @@ -3,22 +3,27 @@ class EventsFinder prepend FinderMethods prepend FinderWithCrossProjectAccess + + MAX_PER_PAGE = 100 + attr_reader :source, :params, :current_user - requires_cross_project_access unless: -> { source.is_a?(Project) } + requires_cross_project_access unless: -> { source.is_a?(Project) }, model: Event # Used to filter Events # # Arguments: # source - which user or project to looks for events on # current_user - only return events for projects visible to this user - # WARNING: does not consider project feature visibility! # params: # action: string # target_type: string # before: datetime # after: datetime - # + # per_page: integer (max. 100) + # page: integer + # with_associations: boolean + # sort: 'asc' or 'desc' def initialize(params = {}) @source = params.delete(:source) @current_user = params.delete(:current_user) @@ -33,15 +38,18 @@ class EventsFinder events = by_target_type(events) events = by_created_at_before(events) events = by_created_at_after(events) + events = sort(events) + + events = events.with_associations if params[:with_associations] - events + paginated_filtered_by_user_visibility(events) end private # rubocop: disable CodeReuse/ActiveRecord def by_current_user_access(events) - events.merge(ProjectsFinder.new(current_user: current_user).execute) # rubocop: disable CodeReuse/Finder + events.merge(Project.public_or_visible_to_user(current_user)) .joins(:project) end # rubocop: enable CodeReuse/ActiveRecord @@ -77,4 +85,31 @@ class EventsFinder events.where('events.created_at > ?', params[:after].end_of_day) end # rubocop: enable CodeReuse/ActiveRecord + + def sort(events) + return events unless params[:sort] + + if params[:sort] == 'asc' + events.order_id_asc + else + events.order_id_desc + end + end + + def paginated_filtered_by_user_visibility(events) + limited_events = events.page(page).per(per_page) + visible_events = limited_events.select { |event| event.visible_to_user?(current_user) } + + Kaminari.paginate_array(visible_events, total_count: events.count) + end + + def per_page + return MAX_PER_PAGE unless params[:per_page] + + [params[:per_page], MAX_PER_PAGE].min + end + + def page + params[:page] || 1 + end end diff --git a/app/models/prometheus_metric.rb b/app/models/prometheus_metric.rb index ce2db9cb44c..defbade1ed6 100644 --- a/app/models/prometheus_metric.rb +++ b/app/models/prometheus_metric.rb @@ -5,11 +5,12 @@ class PrometheusMetric < ActiveRecord::Base enum group: { # built-in groups - nginx_ingress: -1, + nginx_ingress_vts: -1, ha_proxy: -2, aws_elb: -3, nginx: -4, kubernetes: -5, + nginx_ingress: -6, # custom/user groups business: 0, @@ -30,6 +31,7 @@ class PrometheusMetric < ActiveRecord::Base GROUP_TITLES = { # built-in groups + nginx_ingress_vts: _('Response metrics (NGINX Ingress VTS)'), nginx_ingress: _('Response metrics (NGINX Ingress)'), ha_proxy: _('Response metrics (HA Proxy)'), aws_elb: _('Response metrics (AWS ELB)'), @@ -43,7 +45,8 @@ class PrometheusMetric < ActiveRecord::Base }.freeze REQUIRED_METRICS = { - nginx_ingress: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg), + nginx_ingress_vts: %w(nginx_upstream_responses_total nginx_upstream_response_msecs_avg), + nginx_ingress: %w(nginx_ingress_controller_requests nginx_ingress_controller_ingress_upstream_latency_seconds_sum), ha_proxy: %w(haproxy_frontend_http_requests_total haproxy_frontend_http_responses_total), aws_elb: %w(aws_elb_request_count_sum aws_elb_latency_average aws_elb_httpcode_backend_5_xx_sum), nginx: %w(nginx_server_requests nginx_server_requestMsec), diff --git a/app/services/groups/update_service.rb b/app/services/groups/update_service.rb index 0bf0e967dcc..31d3c844ad5 100644 --- a/app/services/groups/update_service.rb +++ b/app/services/groups/update_service.rb @@ -36,7 +36,7 @@ module Groups end def reject_parent_id! - params.except!(:parent_id) + params.delete(:parent_id) end def valid_share_with_group_lock_change? diff --git a/app/services/issuable/common_system_notes_service.rb b/app/services/issuable/common_system_notes_service.rb index 765de9c66b0..885e14bba8f 100644 --- a/app/services/issuable/common_system_notes_service.rb +++ b/app/services/issuable/common_system_notes_service.rb @@ -4,20 +4,23 @@ module Issuable class CommonSystemNotesService < ::BaseService attr_reader :issuable - def execute(issuable, old_labels) + def execute(issuable, old_labels: [], is_update: true) @issuable = issuable - if issuable.previous_changes.include?('title') - create_title_change_note(issuable.previous_changes['title'].first) - end + if is_update + if issuable.previous_changes.include?('title') + create_title_change_note(issuable.previous_changes['title'].first) + end - handle_description_change_note + handle_description_change_note + + handle_time_tracking_note if issuable.is_a?(TimeTrackable) + create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked') + end - handle_time_tracking_note if issuable.is_a?(TimeTrackable) - create_labels_note(old_labels) if issuable.labels != old_labels - create_discussion_lock_note if issuable.previous_changes.include?('discussion_locked') - create_milestone_note if issuable.previous_changes.include?('milestone_id') create_due_date_note if issuable.previous_changes.include?('due_date') + create_milestone_note if issuable.previous_changes.include?('milestone_id') + create_labels_note(old_labels) if issuable.labels != old_labels end private diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index e32e262ac31..c7e7bb55e4b 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -152,6 +152,10 @@ class IssuableBaseService < BaseService before_create(issuable) if issuable.save + ActiveRecord::Base.no_touching do + Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, is_update: false) + end + after_create(issuable) execute_hooks(issuable) invalidate_cache_counts(issuable, users: issuable.assignees) @@ -207,7 +211,7 @@ class IssuableBaseService < BaseService if issuable.with_transaction_returning_status { issuable.save } # We do not touch as it will affect a update on updated_at field ActiveRecord::Base.no_touching do - Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_associations[:labels]) + Issuable::CommonSystemNotesService.new(project, current_user).execute(issuable, old_labels: old_associations[:labels]) end handle_changes(issuable, old_associations: old_associations) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index aacaf10d09c..33d8299c8b6 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -5,14 +5,15 @@ module MergeRequests def execute(merge_request) # We don't allow change of source/target projects and source branch # after merge request was created - params.except!(:source_project_id) - params.except!(:target_project_id) - params.except!(:source_branch) + params.delete(:source_project_id) + params.delete(:target_project_id) + params.delete(:source_branch) merge_from_quick_action(merge_request) if params[:merge] if merge_request.closed_without_fork? - params.except!(:target_branch, :force_remove_source_branch) + params.delete(:target_branch) + params.delete(:force_remove_source_branch) end if params[:force_remove_source_branch].present? diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb index a897e4bd56a..af4fe1aebb9 100644 --- a/app/services/users/update_service.rb +++ b/app/services/users/update_service.rb @@ -16,7 +16,7 @@ module Users user_exists = @user.persisted? - assign_attributes(&block) + assign_attributes if @user.save(validate: validate) && update_status notify_success(user_exists) @@ -48,9 +48,11 @@ module Users success end - def assign_attributes(&block) - if @user.user_synced_attributes_metadata - params.except!(*@user.user_synced_attributes_metadata.read_only_attributes) + def assign_attributes + if (metadata = @user.user_synced_attributes_metadata) + read_only = metadata.read_only_attributes + + params.reject! { |key, _| read_only.include?(key.to_sym) } end @user.assign_attributes(params) if params.any? diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index e191b009db2..82b2ab64a5d 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -2,7 +2,7 @@ - show_auto_devops_callout = show_auto_devops_callout?(@project) .project-home-panel{ class: ("empty-project" if empty_repo) } .project-header.row.append-bottom-8 - .project-title-row.col-md-12.col-lg-7.d-flex + .project-title-row.col-md-12.col-lg-6.d-flex .avatar-container.project-avatar.float-none = project_icon(@project, alt: @project.name, class: 'avatar avatar-tile s64', width: 64, height: 64) .d-flex.flex-column.flex-wrap.align-items-baseline @@ -25,7 +25,7 @@ - if @project.has_extra_tags? = _("+ %{count} more") % { count: @project.count_of_extra_tags_not_shown } - .project-repo-buttons.col-md-12.col-lg-5.d-inline-flex.flex-wrap.justify-content-lg-end + .project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end - if current_user .d-inline-flex = render 'projects/buttons/notifications', notification_setting: @notification_setting, btn_class: 'btn-xs' diff --git a/changelogs/unreleased/51485-new-issue-labels-note.yml b/changelogs/unreleased/51485-new-issue-labels-note.yml new file mode 100644 index 00000000000..a312d379ce2 --- /dev/null +++ b/changelogs/unreleased/51485-new-issue-labels-note.yml @@ -0,0 +1,5 @@ +--- +title: Create system notes on issue / MR creation when labels, milestone, or due date is set +merge_request: 23859 +author: +type: added diff --git a/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml b/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml new file mode 100644 index 00000000000..0632c1992c7 --- /dev/null +++ b/changelogs/unreleased/53954-resolved-non-diff-discussions-on-merge-requests-no-longer-show-who-resolved-them-and-when-at-a-glance.yml @@ -0,0 +1,5 @@ +--- +title: Show message on non-diff discussions +merge_request: +author: +type: changed diff --git a/changelogs/unreleased/add-new-nginx-metrics.yml b/changelogs/unreleased/add-new-nginx-metrics.yml new file mode 100644 index 00000000000..57221056d6e --- /dev/null +++ b/changelogs/unreleased/add-new-nginx-metrics.yml @@ -0,0 +1,5 @@ +--- +title: Add NGINX 0.16.0 and above metrics +merge_request: 22133 +author: +type: added diff --git a/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml b/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml new file mode 100644 index 00000000000..09480499b87 --- /dev/null +++ b/changelogs/unreleased/blackst0ne-improve-encoding-helper-spec.yml @@ -0,0 +1,5 @@ +--- +title: Update specs to exclude possible false positive pass +merge_request: 23893 +author: "@blackst0ne" +type: other diff --git a/changelogs/unreleased/bvl-hide-confidential-events-take2.yml b/changelogs/unreleased/bvl-hide-confidential-events-take2.yml new file mode 100644 index 00000000000..a5abd496a9d --- /dev/null +++ b/changelogs/unreleased/bvl-hide-confidential-events-take2.yml @@ -0,0 +1,5 @@ +--- +title: Hide confidential events in the API +merge_request: 23746 +author: +type: other diff --git a/changelogs/unreleased/none-syntax-highlighting.yml b/changelogs/unreleased/none-syntax-highlighting.yml new file mode 100644 index 00000000000..b373aac7c02 --- /dev/null +++ b/changelogs/unreleased/none-syntax-highlighting.yml @@ -0,0 +1,5 @@ +--- +title: Add no-color theme for syntax highlighting. +merge_request: !20170 +author: khm +type: added diff --git a/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml b/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml new file mode 100644 index 00000000000..206253a100c --- /dev/null +++ b/changelogs/unreleased/sh-carrierwave-patch-google-acl.yml @@ -0,0 +1,5 @@ +--- +title: Fix object storage not working properly with Google S3 compatibility +merge_request: 23858 +author: +type: fixed diff --git a/config/initializers/carrierwave_patch.rb b/config/initializers/carrierwave_patch.rb index 35ffff03abe..c361784491d 100644 --- a/config/initializers/carrierwave_patch.rb +++ b/config/initializers/carrierwave_patch.rb @@ -23,6 +23,19 @@ module CarrierWave end end end + + # Fix for https://github.com/carrierwaveuploader/carrierwave/pull/2356 + def acl_header + if fog_provider == 'AWS' + { 'x-amz-acl' => @uploader.fog_public ? 'public-read' : 'private' } + else + {} + end + end + + def fog_provider + @uploader.fog_credentials[:provider].to_s + end end end end diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 6aba6c7c21d..be4183f39be 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -42,7 +42,9 @@ Sidekiq.configure_server do |config| end if Feature::FlipperFeature.table_exists? && Feature.enabled?(:gitlab_sidekiq_reliable_fetcher) - Sidekiq::ReliableFetcher.setup_reliable_fetch!(config) + # By default we're going to use Semi Reliable Fetch + config.options[:semi_reliable_fetch] = Feature.enabled?(:gitlab_sidekiq_enable_semi_reliable_fetcher, default_enabled: true) + Sidekiq::ReliableFetch.setup_reliable_fetch!(config) end # Sidekiq-cron: load recurring jobs from gitlab.yml diff --git a/config/prometheus/common_metrics.yml b/config/prometheus/common_metrics.yml index 52023a2e3cb..9bdaf1575e9 100644 --- a/config/prometheus/common_metrics.yml +++ b/config/prometheus/common_metrics.yml @@ -1,4 +1,5 @@ -- group: Response metrics (NGINX Ingress) + # NGINX Ingress metrics for pre-0.16.0 versions +- group: Response metrics (NGINX Ingress VTS) priority: 10 metrics: - title: "Throughput" @@ -40,6 +41,51 @@ query_range: 'sum(rate(nginx_upstream_responses_total{status_code="5xx", upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) / sum(rate(nginx_upstream_responses_total{upstream=~"%{kube_namespace}-%{ci_environment_slug}-.*"}[2m])) * 100' label: 5xx Errors unit: "%" +# NGINX Ingress metrics for post-0.16.0 versions +- group: Response metrics (NGINX Ingress) + priority: 10 + metrics: + - title: "Throughput" + y_label: "Requests / Sec" + required_metrics: + - nginx_ingress_controller_requests + weight: 1 + queries: + - id: response_metrics_nginx_ingress_16_throughput_status_code + query_range: 'sum(label_replace(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m]), "status_code", "${1}xx", "status", "(.)..")) by (status_code)' + unit: req / sec + label: Status Code + series: + - label: status_code + when: + - value: 2xx + color: green + - value: 3xx + color: blue + - value: 4xx + color: orange + - value: 5xx + color: red + - title: "Latency" + y_label: "Latency (ms)" + required_metrics: + - nginx_ingress_controller_ingress_upstream_latency_seconds_sum + weight: 1 + queries: + - id: response_metrics_nginx_ingress_16_latency_pod_average + query_range: 'sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_sum{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_ingress_upstream_latency_seconds_count{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 1000' + label: Pod average + unit: ms + - title: "HTTP Error Rate" + y_label: "HTTP Errors" + required_metrics: + - nginx_ingress_controller_requests + weight: 1 + queries: + - id: response_metrics_nginx_ingress_16_http_error_rate + query_range: 'sum(rate(nginx_ingress_controller_requests{status=~"5.*",namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) / sum(rate(nginx_ingress_controller_requests{namespace="%{kube_namespace}",ingress=~".*%{ci_environment_slug}.*"}[2m])) * 100' + label: 5xx Errors + unit: "%" - group: Response metrics (HA Proxy) priority: 10 metrics: diff --git a/db/importers/common_metrics_importer.rb b/db/importers/common_metrics_importer.rb index 6302394d7a6..deadd653ae9 100644 --- a/db/importers/common_metrics_importer.rb +++ b/db/importers/common_metrics_importer.rb @@ -4,11 +4,12 @@ module Importers class PrometheusMetric < ActiveRecord::Base enum group: { # built-in groups - nginx_ingress: -1, + nginx_ingress_vts: -1, ha_proxy: -2, aws_elb: -3, nginx: -4, kubernetes: -5, + nginx_ingress: -6, # custom groups business: 0, @@ -22,6 +23,7 @@ module Importers business: _('Business metrics (Custom)'), response: _('Response metrics (Custom)'), system: _('System metrics (Custom)'), + nginx_ingress_vts: _('Response metrics (NGINX Ingress VTS)'), nginx_ingress: _('Response metrics (NGINX Ingress)'), ha_proxy: _('Response metrics (HA Proxy)'), aws_elb: _('Response metrics (AWS ELB)'), diff --git a/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb b/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb new file mode 100644 index 00000000000..98fafed7912 --- /dev/null +++ b/db/migrate/20181006004100_import_common_metrics_nginx_vts.rb @@ -0,0 +1,15 @@ +class ImportCommonMetricsNginxVts < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + require Rails.root.join('db/importers/common_metrics_importer.rb') + + DOWNTIME = false + + def up + Importers::CommonMetricsImporter.new.execute + end + + def down + # no-op + end +end diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md index f85a1f791f9..476ae8e8a76 100644 --- a/doc/administration/uploads.md +++ b/doc/administration/uploads.md @@ -120,30 +120,7 @@ _The uploads are stored by default in ``` 1. Save the file and [reconfigure GitLab][] for the changes to take effect. -1. Migrate any existing local uploads to the object storage: - - > **Notes:** - > These task complies with the `BATCH` environment variable to process uploads in batch (200 by default). All of the processing will be done in a background worker and requires **no downtime**. - - ```bash - # gitlab-rake gitlab:uploads:migrate[uploader_class, model_class, mount_point] - - # Avatars - gitlab-rake "gitlab:uploads:migrate[AvatarUploader, Project, :avatar]" - gitlab-rake "gitlab:uploads:migrate[AvatarUploader, Group, :avatar]" - gitlab-rake "gitlab:uploads:migrate[AvatarUploader, User, :avatar]" - - # Attachments - gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Note, :attachment]" - gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :logo]" - gitlab-rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :header_logo]" - - # Markdown - gitlab-rake "gitlab:uploads:migrate[FileUploader, Project]" - gitlab-rake "gitlab:uploads:migrate[PersonalFileUploader, Snippet]" - gitlab-rake "gitlab:uploads:migrate[NamespaceFileUploader, Snippet]" - gitlab-rake "gitlab:uploads:migrate[FileUploader, MergeRequest]" - ``` +1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md). --- @@ -168,32 +145,7 @@ _The uploads are stored by default in ``` 1. Save the file and [restart GitLab][] for the changes to take effect. -1. Migrate any existing local uploads to the object storage: - - > **Notes:** - > - These task comply with the `BATCH` environment variable to process uploads in batch (200 by default). All of the processing will be done in a background worker and requires **no downtime**. - > - To migrate in production use `RAILS_ENV=production` environment variable. - - ```bash - # sudo -u git -H bundle exec rake gitlab:uploads:migrate - - # Avatars - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, Project, :avatar]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, Group, :avatar]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AvatarUploader, User, :avatar]" - - # Attachments - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Note, :attachment]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :logo]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[AttachmentUploader, Appearance, :header_logo]" - - # Markdown - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, Project]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[PersonalFileUploader, Snippet]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[NamespaceFileUploader, Snippet]" - sudo -u git -H bundle exec rake "gitlab:uploads:migrate[FileUploader, MergeRequest]" - - ``` +1. Migrate any existing local uploads to the object storage using [`gitlab:uploads:migrate` rake task](raketasks/uploads/migrate.md). [reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure "How to reconfigure Omnibus GitLab" [restart gitlab]: restart_gitlab.md#installations-from-source "How to restart GitLab" diff --git a/doc/api/suggestions.md b/doc/api/suggestions.md new file mode 100644 index 00000000000..9d76ef0c4bf --- /dev/null +++ b/doc/api/suggestions.md @@ -0,0 +1,36 @@ +# Suggest Changes API + +Every API call to suggestions must be authenticated. + +## Applying suggestions + +Applies a suggested patch in a merge request. Users must be +at least [Developer](../user/permissions.md) to perform such action. + +``` +PUT /suggestions/:id/apply +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID of a suggestion | + +```bash +curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/suggestions/5/apply +``` + +Example response: + +```json + { + "id": 36, + "from_original_line": 10, + "to_original_line": 10, + "from_line": 10, + "to_line": 10, + "appliable": false, + "applied": true, + "from_content": " \"--talk-name=org.freedesktop.\",\n", + "to_content": " \"--talk-name=org.free.\",\n \"--talk-name=org.desktop.\",\n" + } +``` diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index acfcd05624d..4df5d8fb54a 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -203,7 +203,7 @@ used for time of the job. The configuration of this feature is covered in jobs, including deploy jobs, but after the restoration of [artifacts](#artifacts). This can be an array or a multi-line string. -`after_script` is used to define the command that will be run after for all +`after_script` is used to define the command that will be run after all jobs, including failed ones. This has to be an array or a multi-line string. The `before_script` and the main `script` are concatenated and run in a single context/container. diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 7885cffd107..b41f401e14c 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -567,7 +567,7 @@ To view the metrics, open the While Auto DevOps provides great defaults to get you started, you can customize almost everything to fit your needs; from custom [buildpacks](#custom-buildpacks), to [`Dockerfile`s](#custom-dockerfile), [Helm charts](#custom-helm-chart), or -even copying the complete [CI/CD configuration](#customizing-gitlab-ci-yml) +even copying the complete [CI/CD configuration](#customizing-gitlab-ciyml) into your project to enable staging and canary deployments, and more. ### Custom buildpacks diff --git a/doc/user/discussions/img/insert_suggestion.png b/doc/user/discussions/img/insert_suggestion.png Binary files differnew file mode 100644 index 00000000000..4bf293b8297 --- /dev/null +++ b/doc/user/discussions/img/insert_suggestion.png diff --git a/doc/user/discussions/img/make_suggestion.png b/doc/user/discussions/img/make_suggestion.png Binary files differnew file mode 100644 index 00000000000..20acc1417da --- /dev/null +++ b/doc/user/discussions/img/make_suggestion.png diff --git a/doc/user/discussions/img/suggestion.png b/doc/user/discussions/img/suggestion.png Binary files differnew file mode 100644 index 00000000000..68a67e6ae5e --- /dev/null +++ b/doc/user/discussions/img/suggestion.png diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index 0f89d261ff6..9379d047fca 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -293,6 +293,51 @@ Once you select one of the filters in a given issue or MR, GitLab will save your preference, so that it will persist when you visit the same page again from any device you're logged into. +## Suggest Changes + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6. + +As a reviewer, you're able to suggest code changes with a simple +markdown syntax in Merge Request Diff discussions. Then, the +Merge Request author (or other users with appropriate +[permission](../permissions.md)) is able to apply these +suggestions with a click, which will generate a commit in +the Merge Request authored by the user that applied them. + +1. Choose a line of code to be changed, add a new comment, then click +on the **Insert suggestion** icon in the toolbar: + + ![Add a new comment](img/insert_suggestion.png) + + > **Note:** + The suggestion will only affect the commented line. Multi-line + suggestions are currently not supported. Will be introduced by + [#53310](https://gitlab.com/gitlab-org/gitlab-ce/issues/53310). + +1. In the comment, add your suggestion to the pre-populated code block: + + ![Add a suggestion into a code block tagged properly](img/make_suggestion.png) + +1. Click **Comment**. + + The suggestions in the comment can be applied by the merge request author + directly from the merge request: + + ![Apply suggestions](img/suggestion.png) + + > **Note:** + Discussions are _not_ automatically resolved. Will be introduced by + [#54405](https://gitlab.com/gitlab-org/gitlab-ce/issues/54405). + +Once the author applies a suggestion, it will be marked with the **Applied** label, +and GitLab will create a new commit with the message `Apply suggestion to <file-name>` +and push the suggested change directly into the codebase in the merge request's branch. +[Developer permission](../permissions.md) is required to do so. + +> **Note:** +Custom commit messages will be introduced by +[#54404](https://gitlab.com/gitlab-org/gitlab-ce/issues/54404). + [ce-5022]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5022 [ce-7125]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7125 [ce-7527]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7527 diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md index adc43921d47..8e03d116b81 100644 --- a/doc/user/group/clusters/index.md +++ b/doc/user/group/clusters/index.md @@ -124,3 +124,11 @@ The result will then be: - The Project cluster will be used for the `test` job. - The Staging cluster will be used for the `deploy to staging` job. - The Production cluster will be used for the `deploy to production` job. + +## Unavailable features + +The following features are not currently available for group-level clusters: + +1. Terminals (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55487)). +1. Pod logs (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)). +1. Deployment boards (see [related issue](https://gitlab.com/gitlab-org/gitlab-ce/issues/55488)). diff --git a/doc/user/markdown.md b/doc/user/markdown.md index debebd4c081..893658290e5 100644 --- a/doc/user/markdown.md +++ b/doc/user/markdown.md @@ -5,11 +5,11 @@ It is not valid for the [GitLab documentation website](https://docs.gitlab.com) nor [GitLab's main website](https://about.gitlab.com), as they both use [Kramdown](https://kramdown.gettalong.org) as their markdown engine. The documentation website uses an extended Kramdown gem, [GitLab Kramdown](https://gitlab.com/gitlab-org/gitlab_kramdown). -Consult the [GitLab Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) for a complete Kramdown reference._ +Consult the [GitLab Kramdown Guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/) for a complete Kramdown reference. ## GitLab Flavored Markdown (GFM) -GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification][commonmark-spec] (which is based on standard Markdown) in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). +GitLab uses "GitLab Flavored Markdown" (GFM). It extends the [CommonMark specification][commonmark-spec] (which is based on standard Markdown) in a few significant ways to add additional useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). You can use GFM in the following areas: @@ -26,8 +26,7 @@ dependency to do so. Please see the [`github-markup` gem readme](https://github. > **Notes:** > -> For the best result, we encourage you to check this document out as [rendered -> by GitLab itself](markdown.md). +> We encourage you to view this document as [rendered by GitLab itself](markdown.md). > > As of 11.1, GitLab uses the [CommonMark Ruby Library][commonmarker] for Markdown processing of all new issues, merge requests, comments, and other Markdown content @@ -142,7 +141,7 @@ GFM will autolink almost any URL you copy and paste into your text: * <a href="irc://irc.freenode.net/gitlab">irc://irc.freenode.net/gitlab</a> * http://localhost:3000 -### Multiline Blockquote +### Multiline blockquote > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#multiline-blockquote @@ -172,7 +171,7 @@ you can quote that without having to manually prepend `>` to every line! <p>you can quote that without having to manually prepend <code>></code> to every line!</p> </blockquote> -### Code and Syntax Highlighting +### Code and syntax highlighting > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#code-and-syntax-highlighting @@ -242,7 +241,7 @@ s = "There is no highlighting for this." But let's throw in a <b>tag</b>. ``` -### Inline Diff +### Inline diff > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#inline-diff @@ -309,7 +308,7 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he Ubuntu 18.04 (like many modern Linux distros) has this font installed by default. -### Special GitLab References +### Special GitLab references GFM recognizes special references. @@ -363,7 +362,7 @@ It also has a shorthand version to reference other projects from the same namesp | `project@9ba12248...b19a04f5` | commit range comparison | | `project~"Some label"` | issues with given label | -### Task Lists +### Task lists > If this is not rendered correctly, see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/user/markdown.md#task-lists @@ -505,6 +504,66 @@ Becomes: For details see the [Mermaid official page][mermaid]. +### Front matter + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/23331) + in GitLab 11.6. + +Front matter is metadata included at the beginning of a markdown document, preceding +its content. This data can be used by static site generators such as [Jekyll](https://jekyllrb.com/docs/front-matter/) and [Hugo](https://gohugo.io/content-management/front-matter/), +and many other applications. + +In GitLab, front matter is only used in Markdown files and wiki pages, not the other places where Markdown formatting is supported. +When you view a Markdown file rendered by GitLab, any front matter is displayed as-is, in a box at the top of the document, before the rendered HTML content. +To view an example, you can toggle between the source and rendered version of a [GitLab documentation file](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/README.md). + +The following delimeters are supported: + +- YAML (`---`): + + ``` + --- + title: About Front Matter + example: + language: yaml + --- + ``` + +- TOML (`+++`): + + ``` + +++ + title = "About Front Matter" + [example] + language = "toml" + +++ + ``` + +- JSON (`;;;`): + + ``` + ;;; + { + "title": "About Front Matter" + "example": { + "language": "json" + } + } + ;;; + ``` + +Other languages are supported by adding a specifier to any of the existing +delimiters. For example: + +``` +---php +$title = "About Front Matter"; +$example = array( + 'language' => "php", +); +--- +``` + ## Standard Markdown ### Headers diff --git a/doc/user/permissions.md b/doc/user/permissions.md index c4a2d5f66e5..ed00f86f9de 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -79,6 +79,7 @@ The following table depicts the various user permission levels in a project. | View approved/blacklisted licenses **[ULTIMATE]** | | | ✓ | ✓ | ✓ | | Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ | | Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ | +| Apply code change suggestions | | | ✓ | ✓ | ✓ | | Use environment terminals | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index ea843054f8e..b98221bea61 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -146,7 +146,8 @@ A few notes: The current permissions on the Cycle Analytics dashboard are: - Public projects - anyone can access -- Private/internal projects - any member (guest level and above) can access +- Internal projects - any authenticated user can access +- Private projects - any member Guest and above can access You can [read more about permissions][permissions] in general. diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md index 85d8d804133..d4f8cf929f6 100644 --- a/doc/user/project/merge_requests/index.md +++ b/doc/user/project/merge_requests/index.md @@ -150,6 +150,16 @@ in a Merge Request. To do so, click the **...** button in the gutter of the Merg ![Comment on any diff file line](img/comment-on-any-diff-line.png) +## Suggest changes + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/18008) in GitLab 11.6. + +As a reviewer, you can add suggestions to change the content in +merge request discussions, and users with appropriate [permission](../../permissions.md) +can easily apply them to the codebase directly from the UI. Read +through the documentation on [Suggest changes](../../discussions/index.md#suggest-changes) +to learn more. + ## Resolve conflicts When a merge request has conflicts, GitLab may provide the option to resolve diff --git a/lib/api/events.rb b/lib/api/events.rb index 44dae57770d..b98aa9f31e1 100644 --- a/lib/api/events.rb +++ b/lib/api/events.rb @@ -18,29 +18,15 @@ module API desc: 'Return events sorted in ascending and descending order' end - RedactedEvent = OpenStruct.new(target_title: 'Confidential event').freeze - - def redact_events(events) - events.map do |event| - if event.visible_to_user?(current_user) - event - else - RedactedEvent - end - end - end - - # rubocop: disable CodeReuse/ActiveRecord - def present_events(events, redact: true) - events = events.reorder(created_at: params[:sort]) - .with_associations - + def present_events(events) events = paginate(events) - events = redact_events(events) if redact present events, with: Entities::Event end - # rubocop: enable CodeReuse/ActiveRecord + + def find_events(source) + EventsFinder.new(params.merge(source: source, current_user: current_user, with_associations: true)).execute + end end resource :events do @@ -55,16 +41,14 @@ module API use :event_filter_params use :sort_params end - # rubocop: disable CodeReuse/ActiveRecord + get do authenticate! - events = EventsFinder.new(params.merge(source: current_user, current_user: current_user)).execute.preload(:author, :target) + events = find_events(current_user) - # Since we're viewing our own events, redaction is unnecessary - present_events(events, redact: false) + present_events(events) end - # rubocop: enable CodeReuse/ActiveRecord end params do @@ -82,16 +66,15 @@ module API use :event_filter_params use :sort_params end - # rubocop: disable CodeReuse/ActiveRecord + get ':id/events' do user = find_user(params[:id]) not_found!('User') unless user - events = EventsFinder.new(params.merge(source: user, current_user: current_user)).execute.preload(:author, :target) + events = find_events(user) present_events(events) end - # rubocop: enable CodeReuse/ActiveRecord end params do @@ -106,13 +89,12 @@ module API use :event_filter_params use :sort_params end - # rubocop: disable CodeReuse/ActiveRecord + get ":id/events" do - events = EventsFinder.new(params.merge(source: user_project, current_user: current_user)).execute.preload(:author, :target) + events = find_events(user_project) present_events(events) end - # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/gitlab/color_schemes.rb b/lib/gitlab/color_schemes.rb index a5e4065cf09..881e5dbc923 100644 --- a/lib/gitlab/color_schemes.rb +++ b/lib/gitlab/color_schemes.rb @@ -12,7 +12,8 @@ module Gitlab Scheme.new(2, 'Dark', 'dark'), Scheme.new(3, 'Solarized Light', 'solarized-light'), Scheme.new(4, 'Solarized Dark', 'solarized-dark'), - Scheme.new(5, 'Monokai', 'monokai') + Scheme.new(5, 'Monokai', 'monokai'), + Scheme.new(6, 'None', 'none') ].freeze # Convenience method to get a space-separated String of all the color scheme diff --git a/lib/gitlab/safe_request_store.rb b/lib/gitlab/safe_request_store.rb index 4e82353adb6..d146913bdb3 100644 --- a/lib/gitlab/safe_request_store.rb +++ b/lib/gitlab/safe_request_store.rb @@ -19,5 +19,13 @@ module Gitlab NULL_STORE end end + + # This method accept an options hash to be compatible with + # ActiveSupport::Cache::Store#write method. The options are + # not passed to the underlying cache implementation because + # RequestStore#write accepts only a key, and value params. + def self.write(key, value, options = nil) + store.write(key, value) + end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index dcd5e3c1409..8a7db438add 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5647,6 +5647,9 @@ msgstr "" msgid "Response metrics (HA Proxy)" msgstr "" +msgid "Response metrics (NGINX Ingress VTS)" +msgstr "" + msgid "Response metrics (NGINX Ingress)" msgstr "" diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb new file mode 100644 index 00000000000..7e8b42e286f --- /dev/null +++ b/qa/qa/specs/features/browser_ui/2_plan/issue/issue_suggestions_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +module QA + context 'Plan' do + describe 'issue suggestions' do + let(:issue_title) { 'Issue Lists are awesome' } + + it 'user sees issue suggestions when creating a new issue' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.perform(&:sign_in_using_credentials) + + project = Resource::Project.fabricate! do |resource| + resource.name = 'project-for-issue-suggestions' + resource.description = 'project for issue suggestions' + end + + Resource::Issue.fabricate! do |issue| + issue.title = issue_title + issue.project = project + end + + project.visit! + + Page::Project::Show.perform(&:go_to_new_issue) + Page::Project::Issue::New.perform do |new_issue_page| + new_issue_page.add_title("issue") + expect(new_issue_page).to have_content(issue_title) + + new_issue_page.add_title("Issue Board") + expect(new_issue_page).not_to have_content(issue_title) + end + end + end + end +end diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh index b50bf2161cb..8f9414cc1ed 100755 --- a/scripts/review_apps/review-apps.sh +++ b/scripts/review_apps/review-apps.sh @@ -292,7 +292,7 @@ function get_job_id() { local job_id=$(curl --silent --show-error --header "PRIVATE-TOKEN: ${API_TOKEN}" "${url}" | jq "map(select(.name == \"${job_name}\")) | map(.id) | last") [[ "${job_id}" == "null" && "${page}" -lt "$max_page" ]] || break - ((page++)) + let "page++" done if [[ "${job_id}" == "" ]]; then @@ -334,7 +334,7 @@ function wait_for_job_to_be_done() { [[ "${job_status}" == "pending" || "${job_status}" == "running" ]] || break printf "." - ((elapsed_seconds+=$interval)) + let "elapsed_seconds+=interval" sleep ${interval} done diff --git a/scripts/trigger-build b/scripts/trigger-build index 14af3281106..4032ba853e6 100755 --- a/scripts/trigger-build +++ b/scripts/trigger-build @@ -68,7 +68,7 @@ module Trigger def base_variables { - 'GITLAB_REF_SLUG' => ref_slug, + 'GITLAB_REF_SLUG' => ENV['CI_COMMIT_REF_SLUG'], 'TRIGGERED_USER' => ENV['TRIGGERED_USER'] || ENV['GITLAB_USER_NAME'], 'TRIGGER_SOURCE' => ENV['CI_JOB_URL'], 'TOP_UPSTREAM_SOURCE_PROJECT' => ENV['CI_PROJECT_PATH'], @@ -77,12 +77,6 @@ module Trigger } end - def ref_slug - return 'master' if ENV['CI_COMMIT_REF_SLUG'] =~ %r{(\Aqa[/-]|-qa\z)} - - ENV['CI_COMMIT_REF_SLUG'] - end - # Read version files from all components def version_file_variables Dir.glob("*_VERSION").each_with_object({}) do |version_file, params| diff --git a/spec/finders/concerns/finder_with_cross_project_access_spec.rb b/spec/finders/concerns/finder_with_cross_project_access_spec.rb index 1ff65a8101b..f29acb521a8 100644 --- a/spec/finders/concerns/finder_with_cross_project_access_spec.rb +++ b/spec/finders/concerns/finder_with_cross_project_access_spec.rb @@ -115,4 +115,20 @@ describe FinderWithCrossProjectAccess do expect(finder.execute).to include(result) end end + + context 'when specifying a model' do + let(:finder_class) do + Class.new do + prepend FinderWithCrossProjectAccess + + requires_cross_project_access model: Project + end + end + + context '.finder_model' do + it 'is set correctly' do + expect(finder_class.finder_model).to eq(Project) + end + end + end end diff --git a/spec/finders/events_finder_spec.rb b/spec/finders/events_finder_spec.rb index 62968e83292..3bce46cc4d1 100644 --- a/spec/finders/events_finder_spec.rb +++ b/spec/finders/events_finder_spec.rb @@ -14,6 +14,10 @@ describe EventsFinder do let!(:closed_issue_event2) { create(:event, project: project1, author: user, target: closed_issue, action: Event::CLOSED, created_at: Date.new(2016, 2, 2)) } let!(:opened_merge_request_event2) { create(:event, project: project2, author: user, target: opened_merge_request, action: Event::CREATED, created_at: Date.new(2017, 2, 2)) } + let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) } + let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) } + let!(:confidential_event) { create(:event, project: public_project, author: user, target: confidential_issue, action: Event::CLOSED) } + context 'when targeting a user' do it 'returns events between specified dates filtered on action and type' do events = described_class.new(source: user, current_user: user, action: 'created', target_type: 'merge_request', after: Date.new(2017, 1, 1), before: Date.new(2017, 2, 1)).execute @@ -27,6 +31,19 @@ describe EventsFinder do expect(events).not_to include(opened_merge_request_event) end + it 'does not include events on confidential issues the user does not have access to' do + events = described_class.new(source: user, current_user: other_user).execute + + expect(events).not_to include(confidential_event) + end + + it 'includes confidential events user has access to' do + public_project.add_developer(other_user) + events = described_class.new(source: user, current_user: other_user).execute + + expect(events).to include(confidential_event) + end + it 'returns nothing when the current user cannot read cross project' do expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } diff --git a/spec/finders/user_recent_events_finder_spec.rb b/spec/finders/user_recent_events_finder_spec.rb index c5fcd68eb4c..5ebceeb7586 100644 --- a/spec/finders/user_recent_events_finder_spec.rb +++ b/spec/finders/user_recent_events_finder_spec.rb @@ -29,8 +29,9 @@ describe UserRecentEventsFinder do end it 'does not include the events if the user cannot read cross project' do - expect(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).and_call_original expect(Ability).to receive(:allowed?).with(current_user, :read_cross_project) { false } + expect(finder.execute).to be_empty end end diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index a5bf2f2b3df..429816efec3 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -124,7 +124,7 @@ describe Gitlab::EncodingHelper do end it 'returns empty string on conversion errors' do - expect { ext_class.encode_utf8('') }.not_to raise_error(ArgumentError) + expect { ext_class.encode_utf8('') }.not_to raise_error end context 'with strings that can be forcefully encoded into utf8' do diff --git a/spec/lib/gitlab/gpg_spec.rb b/spec/lib/gitlab/gpg_spec.rb index 39d09c49989..48bbd7f854c 100644 --- a/spec/lib/gitlab/gpg_spec.rb +++ b/spec/lib/gitlab/gpg_spec.rb @@ -137,7 +137,7 @@ describe Gitlab::Gpg do described_class.using_tmp_keychain do end end - end.not_to raise_error(ThreadError) + end.not_to raise_error end end end diff --git a/spec/lib/gitlab/safe_request_store_spec.rb b/spec/lib/gitlab/safe_request_store_spec.rb index c797171dbe2..bae87e43615 100644 --- a/spec/lib/gitlab/safe_request_store_spec.rb +++ b/spec/lib/gitlab/safe_request_store_spec.rb @@ -78,6 +78,12 @@ describe Gitlab::SafeRequestStore do described_class.write('foo', true) end.to change { described_class.read('foo') }.from(nil).to(true) end + + it 'does not pass the options hash to the underlying store implementation' do + expect(described_class.store).to receive(:write).with('foo', true) + + described_class.write('foo', true, expires_in: 15.seconds) + end end context 'when RequestStore is NOT active' do @@ -86,6 +92,12 @@ describe Gitlab::SafeRequestStore do described_class.write('foo', true) end.not_to change { described_class.read('foo') }.from(nil) end + + it 'does not pass the options hash to the underlying store implementation' do + expect(described_class.store).to receive(:write).with('foo', true) + + described_class.write('foo', true, expires_in: 15.seconds) + end end end diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index b14b773b653..51221e07ca3 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -43,7 +43,7 @@ shared_examples 'ChronicDurationAttribute writer' do end it "doesn't raise exception" do - expect { subject.send("#{virtual_field}=", '-10m') }.not_to raise_error(ChronicDuration::DurationParseError) + expect { subject.send("#{virtual_field}=", '-10m') }.not_to raise_error end it "doesn't change value" do @@ -87,7 +87,7 @@ shared_examples 'ChronicDurationAttribute writer' do end it "doesn't raise exception" do - expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error(NoMethodError) + expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index bf4117fbcaf..7fd5307b906 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -1358,7 +1358,7 @@ describe MergeRequest do it 'does not raises a NameError exception' do allow_any_instance_of(service_class_name.constantize).to receive(:execute).and_return(nil) - expect { subject }.not_to raise_error(NameError) + expect { subject }.not_to raise_error end end end diff --git a/spec/requests/api/events_spec.rb b/spec/requests/api/events_spec.rb index 573eebe314d..c4e3ac14441 100644 --- a/spec/requests/api/events_spec.rb +++ b/spec/requests/api/events_spec.rb @@ -182,6 +182,68 @@ describe API::Events do end end + context 'with inaccessible events' do + let(:public_project) { create(:project, :public, creator_id: user.id, namespace: user.namespace) } + let(:confidential_issue) { create(:closed_issue, confidential: true, project: public_project, author: user) } + let!(:confidential_event) { create(:event, project: public_project, author: user, target: confidential_issue, action: Event::CLOSED) } + let(:public_issue) { create(:closed_issue, project: public_project, author: user) } + let!(:public_event) { create(:event, project: public_project, author: user, target: public_issue, action: Event::CLOSED) } + + it 'returns only accessible events' do + get api("/projects/#{public_project.id}/events", non_member) + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to eq(1) + end + + it 'returns all events when the user has access' do + get api("/projects/#{public_project.id}/events", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response.size).to eq(2) + end + end + + context 'pagination' do + let(:public_project) { create(:project, :public) } + + before do + create(:event, + project: public_project, + target: create(:issue, project: public_project, title: 'Issue 1'), + action: Event::CLOSED, + created_at: Date.parse('2018-12-10')) + create(:event, + project: public_project, + target: create(:issue, confidential: true, project: public_project, title: 'Confidential event'), + action: Event::CLOSED, + created_at: Date.parse('2018-12-11')) + create(:event, + project: public_project, + target: create(:issue, project: public_project, title: 'Issue 2'), + action: Event::CLOSED, + created_at: Date.parse('2018-12-12')) + end + + it 'correctly returns the second page without inaccessible events' do + get api("/projects/#{public_project.id}/events", user), per_page: 2, page: 2 + + titles = json_response.map { |event| event['target_title'] } + + expect(titles.first).to eq('Issue 1') + expect(titles).not_to include('Confidential event') + end + + it 'correctly returns the first page without inaccessible events' do + get api("/projects/#{public_project.id}/events", user), per_page: 2, page: 1 + + titles = json_response.map { |event| event['target_title'] } + + expect(titles.first).to eq('Issue 2') + expect(titles).not_to include('Confidential event') + end + end + context 'when not permitted to read' do it 'returns 404' do get api("/projects/#{private_project.id}/events", non_member) diff --git a/spec/requests/api/redacted_events_spec.rb b/spec/requests/api/redacted_events_spec.rb deleted file mode 100644 index 086dd3df9ba..00000000000 --- a/spec/requests/api/redacted_events_spec.rb +++ /dev/null @@ -1,68 +0,0 @@ -require 'spec_helper' - -describe 'Redacted events in API::Events' do - shared_examples 'private events are redacted' do - it 'redacts events the user does not have access to' do - expect_any_instance_of(Event).to receive(:visible_to_user?).and_call_original - - get api(path), user - - expect(response).to have_gitlab_http_status(200) - expect(json_response).to contain_exactly( - 'project_id' => nil, - 'action_name' => nil, - 'target_id' => nil, - 'target_iid' => nil, - 'target_type' => nil, - 'author_id' => nil, - 'target_title' => 'Confidential event', - 'created_at' => nil, - 'author_username' => nil - ) - end - end - - describe '/users/:id/events' do - let(:project) { create(:project, :public) } - let(:path) { "/users/#{project.owner.id}/events" } - let(:issue) { create(:issue, :confidential, project: project) } - - before do - EventCreateService.new.open_issue(issue, issue.author) - end - - context 'unauthenticated user views another user with private events' do - let(:user) { nil } - - include_examples 'private events are redacted' - end - - context 'authenticated user without access views another user with private events' do - let(:user) { create(:user) } - - include_examples 'private events are redacted' - end - end - - describe '/projects/:id/events' do - let(:project) { create(:project, :public) } - let(:path) { "/projects/#{project.id}/events" } - let(:issue) { create(:issue, :confidential, project: project) } - - before do - EventCreateService.new.open_issue(issue, issue.author) - end - - context 'unauthenticated user views public project' do - let(:user) { nil } - - include_examples 'private events are redacted' - end - - context 'authenticated user without access views public project' do - let(:user) { create(:user) } - - include_examples 'private events are redacted' - end - end -end diff --git a/spec/services/issuable/common_system_notes_service_spec.rb b/spec/services/issuable/common_system_notes_service_spec.rb index fa1a421d528..fa5d5ebac5c 100644 --- a/spec/services/issuable/common_system_notes_service_spec.rb +++ b/spec/services/issuable/common_system_notes_service_spec.rb @@ -5,7 +5,7 @@ describe Issuable::CommonSystemNotesService do let(:project) { create(:project) } let(:issuable) { create(:issue) } - describe '#execute' do + context 'on issuable update' do it_behaves_like 'system note creation', { title: 'New title' }, 'changed title' it_behaves_like 'system note creation', { description: 'New description' }, 'changed the description' it_behaves_like 'system note creation', { discussion_locked: true }, 'locked this issue' @@ -20,7 +20,7 @@ describe Issuable::CommonSystemNotesService do end it 'creates a resource label event' do - described_class.new(project, user).execute(issuable, []) + described_class.new(project, user).execute(issuable, old_labels: []) event = issuable.reload.resource_label_events.last expect(event).not_to be_nil @@ -68,4 +68,47 @@ describe Issuable::CommonSystemNotesService do end end end + + context 'on issuable create' do + let(:issuable) { build(:issue) } + + subject { described_class.new(project, user).execute(issuable, old_labels: [], is_update: false) } + + it 'does not create system note for title and description' do + issuable.save + + expect { subject }.not_to change { issuable.notes.count } + end + + it 'creates a resource label event for labels added' do + label = create(:label, project: project) + + issuable.labels << label + issuable.save + + expect { subject }.to change { issuable.resource_label_events.count }.from(0).to(1) + + event = issuable.reload.resource_label_events.last + + expect(event).not_to be_nil + expect(event.label_id).to eq label.id + expect(event.user_id).to eq user.id + end + + it 'creates a system note for milestone set' do + issuable.milestone = create(:milestone, project: project) + issuable.save + + expect { subject }.to change { issuable.notes.count }.from(0).to(1) + expect(issuable.notes.last.note).to match('changed milestone') + end + + it 'creates a system note for due_date set' do + issuable.due_date = Date.today + issuable.save + + expect { subject }.to change { issuable.notes.count }.from(0).to(1) + expect(issuable.notes.last.note).to match('changed due date') + end + end end diff --git a/spec/support/shared_examples/common_system_notes_examples.rb b/spec/support/shared_examples/common_system_notes_examples.rb index 96ef30b7513..da5a4f3e319 100644 --- a/spec/support/shared_examples/common_system_notes_examples.rb +++ b/spec/support/shared_examples/common_system_notes_examples.rb @@ -1,5 +1,5 @@ shared_examples 'system note creation' do |update_params, note_text| - subject { described_class.new(project, user).execute(issuable, [])} + subject { described_class.new(project, user).execute(issuable, old_labels: []) } before do issuable.assign_attributes(update_params) @@ -16,7 +16,7 @@ shared_examples 'system note creation' do |update_params, note_text| end shared_examples 'WIP notes creation' do |wip_action| - subject { described_class.new(project, user).execute(issuable, []) } + subject { described_class.new(project, user).execute(issuable, old_labels: []) } it 'creates WIP toggle and title change notes' do expect { subject }.to change { Note.count }.from(0).to(2) |