Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									ea3306a15e
								
							
						
					
					
						commit
						8f534e1e96
					
				|  | @ -1,5 +1,5 @@ | ||||||
| <script> | <script> | ||||||
| import { GlLink, GlPopover, GlSprintf, GlTooltipDirective } from '@gitlab/ui'; | import { GlLink, GlPopover, GlSprintf, GlTooltipDirective, GlBadge } from '@gitlab/ui'; | ||||||
| import { SCHEDULE_ORIGIN } from '../../constants'; | import { SCHEDULE_ORIGIN } from '../../constants'; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|  | @ -7,6 +7,7 @@ export default { | ||||||
|     GlLink, |     GlLink, | ||||||
|     GlPopover, |     GlPopover, | ||||||
|     GlSprintf, |     GlSprintf, | ||||||
|  |     GlBadge, | ||||||
|   }, |   }, | ||||||
|   directives: { |   directives: { | ||||||
|     GlTooltip: GlTooltipDirective, |     GlTooltip: GlTooltipDirective, | ||||||
|  | @ -57,46 +58,49 @@ export default { | ||||||
|     </gl-link> |     </gl-link> | ||||||
|     <div class="label-container"> |     <div class="label-container"> | ||||||
|       <gl-link v-if="isScheduled" :href="pipelineScheduleUrl" target="__blank"> |       <gl-link v-if="isScheduled" :href="pipelineScheduleUrl" target="__blank"> | ||||||
|         <span |         <gl-badge | ||||||
|           v-gl-tooltip |           v-gl-tooltip | ||||||
|           :title="__('This pipeline was triggered by a schedule.')" |           :title="__('This pipeline was triggered by a schedule.')" | ||||||
|           class="badge badge-info" |           variant="info" | ||||||
|  |           size="sm" | ||||||
|           data-testid="pipeline-url-scheduled" |           data-testid="pipeline-url-scheduled" | ||||||
|           >{{ __('Scheduled') }}</span |           >{{ __('Scheduled') }}</gl-badge | ||||||
|         > |         > | ||||||
|       </gl-link> |       </gl-link> | ||||||
|       <span |       <gl-badge | ||||||
|         v-if="pipeline.flags.latest" |         v-if="pipeline.flags.latest" | ||||||
|         v-gl-tooltip |         v-gl-tooltip | ||||||
|         :title="__('Latest pipeline for the most recent commit on this branch')" |         :title="__('Latest pipeline for the most recent commit on this branch')" | ||||||
|         class="badge badge-success" |         variant="success" | ||||||
|  |         size="sm" | ||||||
|         data-testid="pipeline-url-latest" |         data-testid="pipeline-url-latest" | ||||||
|         >{{ __('latest') }}</span |         >{{ __('latest') }}</gl-badge | ||||||
|       > |       > | ||||||
|       <span |       <gl-badge | ||||||
|         v-if="pipeline.flags.yaml_errors" |         v-if="pipeline.flags.yaml_errors" | ||||||
|         v-gl-tooltip |         v-gl-tooltip | ||||||
|         :title="pipeline.yaml_errors" |         :title="pipeline.yaml_errors" | ||||||
|         class="badge badge-danger" |         variant="danger" | ||||||
|  |         size="sm" | ||||||
|         data-testid="pipeline-url-yaml" |         data-testid="pipeline-url-yaml" | ||||||
|         >{{ __('yaml invalid') }}</span |         >{{ __('yaml invalid') }}</gl-badge | ||||||
|       > |       > | ||||||
|       <span |       <gl-badge | ||||||
|         v-if="pipeline.flags.failure_reason" |         v-if="pipeline.flags.failure_reason" | ||||||
|         v-gl-tooltip |         v-gl-tooltip | ||||||
|         :title="pipeline.failure_reason" |         :title="pipeline.failure_reason" | ||||||
|         class="badge badge-danger" |         variant="danger" | ||||||
|  |         size="sm" | ||||||
|         data-testid="pipeline-url-failure" |         data-testid="pipeline-url-failure" | ||||||
|         >{{ __('error') }}</span |         >{{ __('error') }}</gl-badge | ||||||
|       > |       > | ||||||
|       <gl-link |       <gl-link | ||||||
|         v-if="pipeline.flags.auto_devops" |         v-if="pipeline.flags.auto_devops" | ||||||
|         :id="`pipeline-url-autodevops-${pipeline.id}`" |         :id="`pipeline-url-autodevops-${pipeline.id}`" | ||||||
|         tabindex="0" |         tabindex="0" | ||||||
|         class="badge badge-info autodevops-badge" |  | ||||||
|         data-testid="pipeline-url-autodevops" |         data-testid="pipeline-url-autodevops" | ||||||
|         role="button" |         role="button" | ||||||
|         >{{ __('Auto DevOps') }}</gl-link |         ><gl-badge variant="info" size="sm">{{ __('Auto DevOps') }}</gl-badge></gl-link | ||||||
|       > |       > | ||||||
|       <gl-popover |       <gl-popover | ||||||
|         :target="`pipeline-url-autodevops-${pipeline.id}`" |         :target="`pipeline-url-autodevops-${pipeline.id}`" | ||||||
|  | @ -122,13 +126,14 @@ export default { | ||||||
|           __('Learn more about Auto DevOps') |           __('Learn more about Auto DevOps') | ||||||
|         }}</gl-link> |         }}</gl-link> | ||||||
|       </gl-popover> |       </gl-popover> | ||||||
|       <span |       <gl-badge | ||||||
|         v-if="pipeline.flags.stuck" |         v-if="pipeline.flags.stuck" | ||||||
|         class="badge badge-warning" |         variant="warning" | ||||||
|  |         size="sm" | ||||||
|         data-testid="pipeline-url-stuck" |         data-testid="pipeline-url-stuck" | ||||||
|         >{{ __('stuck') }}</span |         >{{ __('stuck') }}</gl-badge | ||||||
|       > |       > | ||||||
|       <span |       <gl-badge | ||||||
|         v-if="pipeline.flags.detached_merge_request_pipeline" |         v-if="pipeline.flags.detached_merge_request_pipeline" | ||||||
|         v-gl-tooltip |         v-gl-tooltip | ||||||
|         :title=" |         :title=" | ||||||
|  | @ -136,17 +141,19 @@ export default { | ||||||
|             'Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.', |             'Pipelines for merge requests are configured. A detached pipeline runs in the context of the merge request, and not against the merged result. Learn more in the documentation for Pipelines for Merged Results.', | ||||||
|           ) |           ) | ||||||
|         " |         " | ||||||
|         class="badge badge-info" |         variant="info" | ||||||
|  |         size="sm" | ||||||
|         data-testid="pipeline-url-detached" |         data-testid="pipeline-url-detached" | ||||||
|         >{{ __('detached') }}</span |         >{{ __('detached') }}</gl-badge | ||||||
|       > |       > | ||||||
|       <span |       <gl-badge | ||||||
|         v-if="isInFork" |         v-if="isInFork" | ||||||
|         v-gl-tooltip |         v-gl-tooltip | ||||||
|         :title="__('Pipeline ran in fork of project')" |         :title="__('Pipeline ran in fork of project')" | ||||||
|         class="badge badge-info" |         variant="info" | ||||||
|  |         size="sm" | ||||||
|         data-testid="pipeline-url-fork" |         data-testid="pipeline-url-fork" | ||||||
|         >{{ __('fork') }}</span |         >{{ __('fork') }}</gl-badge | ||||||
|       > |       > | ||||||
|     </div> |     </div> | ||||||
|   </div> |   </div> | ||||||
|  |  | ||||||
|  | @ -1,80 +0,0 @@ | ||||||
| <script> |  | ||||||
| import { GlTabs, GlTab } from '@gitlab/ui'; |  | ||||||
| import PipelineCharts from './pipeline_charts.vue'; |  | ||||||
| 
 |  | ||||||
| export default { |  | ||||||
|   components: { |  | ||||||
|     GlTabs, |  | ||||||
|     GlTab, |  | ||||||
|     PipelineCharts, |  | ||||||
|     DeploymentFrequencyCharts: () => |  | ||||||
|       import('ee_component/projects/pipelines/charts/components/deployment_frequency_charts.vue'), |  | ||||||
|   }, |  | ||||||
|   inject: { |  | ||||||
|     shouldRenderDeploymentFrequencyCharts: { |  | ||||||
|       type: Boolean, |  | ||||||
|       default: false, |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   props: { |  | ||||||
|     counts: { |  | ||||||
|       type: Object, |  | ||||||
|       required: true, |  | ||||||
|     }, |  | ||||||
|     timesChartData: { |  | ||||||
|       type: Object, |  | ||||||
|       required: true, |  | ||||||
|     }, |  | ||||||
|     lastWeekChartData: { |  | ||||||
|       type: Object, |  | ||||||
|       required: true, |  | ||||||
|     }, |  | ||||||
|     lastMonthChartData: { |  | ||||||
|       type: Object, |  | ||||||
|       required: true, |  | ||||||
|     }, |  | ||||||
|     lastYearChartData: { |  | ||||||
|       type: Object, |  | ||||||
|       required: true, |  | ||||||
|     }, |  | ||||||
|   }, |  | ||||||
|   data() { |  | ||||||
|     return { |  | ||||||
|       // this loading flag gives the echarts library just enough time |  | ||||||
|       // to ensure all DOM nodes have been mounted. |  | ||||||
|       // |  | ||||||
|       // https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1131 |  | ||||||
|       loading: true, |  | ||||||
|     }; |  | ||||||
|   }, |  | ||||||
|   async mounted() { |  | ||||||
|     await this.$nextTick(); |  | ||||||
|     this.loading = false; |  | ||||||
|   }, |  | ||||||
| }; |  | ||||||
| </script> |  | ||||||
| <template> |  | ||||||
|   <gl-tabs v-if="shouldRenderDeploymentFrequencyCharts"> |  | ||||||
|     <gl-tab :title="__('Pipelines')"> |  | ||||||
|       <pipeline-charts |  | ||||||
|         :counts="counts" |  | ||||||
|         :last-week="lastWeekChartData" |  | ||||||
|         :last-month="lastMonthChartData" |  | ||||||
|         :last-year="lastYearChartData" |  | ||||||
|         :times-chart="timesChartData" |  | ||||||
|         :loading="loading" |  | ||||||
|       /> |  | ||||||
|     </gl-tab> |  | ||||||
|     <gl-tab :title="__('Deployments')"> |  | ||||||
|       <deployment-frequency-charts /> |  | ||||||
|     </gl-tab> |  | ||||||
|   </gl-tabs> |  | ||||||
|   <pipeline-charts |  | ||||||
|     v-else |  | ||||||
|     :counts="counts" |  | ||||||
|     :last-week="lastWeekChartData" |  | ||||||
|     :last-month="lastMonthChartData" |  | ||||||
|     :last-year="lastYearChartData" |  | ||||||
|     :times-chart="timesChartData" |  | ||||||
|   /> |  | ||||||
| </template> |  | ||||||
|  | @ -2,7 +2,6 @@ import Vue from 'vue'; | ||||||
| import VueApollo from 'vue-apollo'; | import VueApollo from 'vue-apollo'; | ||||||
| import createDefaultClient from '~/lib/graphql'; | import createDefaultClient from '~/lib/graphql'; | ||||||
| import { parseBoolean } from '~/lib/utils/common_utils'; | import { parseBoolean } from '~/lib/utils/common_utils'; | ||||||
| import ProjectPipelinesChartsLegacy from './components/app_legacy.vue'; |  | ||||||
| import ProjectPipelinesCharts from './components/app.vue'; | import ProjectPipelinesCharts from './components/app.vue'; | ||||||
| 
 | 
 | ||||||
| Vue.use(VueApollo); | Vue.use(VueApollo); | ||||||
|  | @ -12,51 +11,12 @@ const apolloProvider = new VueApollo({ | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| const mountPipelineChartsApp = (el) => { | const mountPipelineChartsApp = (el) => { | ||||||
|   // Not all of the values will be defined since some them will be
 |   const { projectPath } = el.dataset; | ||||||
|   // empty depending on the value of the graphql_pipeline_analytics
 |  | ||||||
|   // feature flag, once the rollout of the feature flag is completed
 |  | ||||||
|   // the undefined values will be deleted
 |  | ||||||
|   const { |  | ||||||
|     countsFailed, |  | ||||||
|     countsSuccess, |  | ||||||
|     countsTotal, |  | ||||||
|     countsTotalDuration, |  | ||||||
|     successRatio, |  | ||||||
|     timesChartLabels, |  | ||||||
|     timesChartValues, |  | ||||||
|     lastWeekChartLabels, |  | ||||||
|     lastWeekChartTotals, |  | ||||||
|     lastWeekChartSuccess, |  | ||||||
|     lastMonthChartLabels, |  | ||||||
|     lastMonthChartTotals, |  | ||||||
|     lastMonthChartSuccess, |  | ||||||
|     lastYearChartLabels, |  | ||||||
|     lastYearChartTotals, |  | ||||||
|     lastYearChartSuccess, |  | ||||||
|     projectPath, |  | ||||||
|   } = el.dataset; |  | ||||||
| 
 | 
 | ||||||
|   const shouldRenderDeploymentFrequencyCharts = parseBoolean( |   const shouldRenderDeploymentFrequencyCharts = parseBoolean( | ||||||
|     el.dataset.shouldRenderDeploymentFrequencyCharts, |     el.dataset.shouldRenderDeploymentFrequencyCharts, | ||||||
|   ); |   ); | ||||||
| 
 | 
 | ||||||
|   const parseAreaChartData = (labels, totals, success) => { |  | ||||||
|     let parsedData = {}; |  | ||||||
| 
 |  | ||||||
|     try { |  | ||||||
|       parsedData = { |  | ||||||
|         labels: JSON.parse(labels), |  | ||||||
|         totals: JSON.parse(totals), |  | ||||||
|         success: JSON.parse(success), |  | ||||||
|       }; |  | ||||||
|     } catch { |  | ||||||
|       parsedData = {}; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     return parsedData; |  | ||||||
|   }; |  | ||||||
| 
 |  | ||||||
|   if (gon?.features?.graphqlPipelineAnalytics) { |  | ||||||
|   return new Vue({ |   return new Vue({ | ||||||
|     el, |     el, | ||||||
|     name: 'ProjectPipelinesChartsApp', |     name: 'ProjectPipelinesChartsApp', | ||||||
|  | @ -70,50 +30,6 @@ const mountPipelineChartsApp = (el) => { | ||||||
|     }, |     }, | ||||||
|     render: (createElement) => createElement(ProjectPipelinesCharts, {}), |     render: (createElement) => createElement(ProjectPipelinesCharts, {}), | ||||||
|   }); |   }); | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   return new Vue({ |  | ||||||
|     el, |  | ||||||
|     name: 'ProjectPipelinesChartsAppLegacy', |  | ||||||
|     components: { |  | ||||||
|       ProjectPipelinesChartsLegacy, |  | ||||||
|     }, |  | ||||||
|     provide: { |  | ||||||
|       projectPath, |  | ||||||
|       shouldRenderDeploymentFrequencyCharts, |  | ||||||
|     }, |  | ||||||
|     render: (createElement) => |  | ||||||
|       createElement(ProjectPipelinesChartsLegacy, { |  | ||||||
|         props: { |  | ||||||
|           counts: { |  | ||||||
|             failed: countsFailed, |  | ||||||
|             success: countsSuccess, |  | ||||||
|             total: countsTotal, |  | ||||||
|             successRatio, |  | ||||||
|             totalDuration: countsTotalDuration, |  | ||||||
|           }, |  | ||||||
|           timesChartData: { |  | ||||||
|             labels: JSON.parse(timesChartLabels), |  | ||||||
|             values: JSON.parse(timesChartValues), |  | ||||||
|           }, |  | ||||||
|           lastWeekChartData: parseAreaChartData( |  | ||||||
|             lastWeekChartLabels, |  | ||||||
|             lastWeekChartTotals, |  | ||||||
|             lastWeekChartSuccess, |  | ||||||
|           ), |  | ||||||
|           lastMonthChartData: parseAreaChartData( |  | ||||||
|             lastMonthChartLabels, |  | ||||||
|             lastMonthChartTotals, |  | ||||||
|             lastMonthChartSuccess, |  | ||||||
|           ), |  | ||||||
|           lastYearChartData: parseAreaChartData( |  | ||||||
|             lastYearChartLabels, |  | ||||||
|             lastYearChartTotals, |  | ||||||
|             lastYearChartSuccess, |  | ||||||
|           ), |  | ||||||
|         }, |  | ||||||
|       }), |  | ||||||
|   }); |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default () => { | export default () => { | ||||||
|  |  | ||||||
|  | @ -17,7 +17,6 @@ class Projects::PipelinesController < Projects::ApplicationController | ||||||
|     push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: true) |     push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: true) | ||||||
|     push_frontend_feature_flag(:graphql_pipeline_header, project, type: :development, default_enabled: false) |     push_frontend_feature_flag(:graphql_pipeline_header, project, type: :development, default_enabled: false) | ||||||
|     push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: false) |     push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: false) | ||||||
|     push_frontend_feature_flag(:graphql_pipeline_analytics, project, type: :development) |  | ||||||
|     push_frontend_feature_flag(:new_pipeline_form_prefilled_vars, project, type: :development, default_enabled: true) |     push_frontend_feature_flag(:new_pipeline_form_prefilled_vars, project, type: :development, default_enabled: true) | ||||||
|   end |   end | ||||||
|   before_action :ensure_pipeline, only: [:show] |   before_action :ensure_pipeline, only: [:show] | ||||||
|  | @ -189,23 +188,6 @@ class Projects::PipelinesController < Projects::ApplicationController | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def charts |  | ||||||
|     @charts = {} |  | ||||||
|     @counts = {} |  | ||||||
| 
 |  | ||||||
|     return if Feature.enabled?(:graphql_pipeline_analytics) |  | ||||||
| 
 |  | ||||||
|     @charts[:week] = Gitlab::Ci::Charts::WeekChart.new(project) |  | ||||||
|     @charts[:month] = Gitlab::Ci::Charts::MonthChart.new(project) |  | ||||||
|     @charts[:year] = Gitlab::Ci::Charts::YearChart.new(project) |  | ||||||
|     @charts[:pipeline_times] = Gitlab::Ci::Charts::PipelineTime.new(project) |  | ||||||
| 
 |  | ||||||
|     @counts[:total] = @project.all_pipelines.count(:all) |  | ||||||
|     @counts[:success] = @project.all_pipelines.success.count(:all) |  | ||||||
|     @counts[:failed] = @project.all_pipelines.failed.count(:all) |  | ||||||
|     @counts[:total_duration] = @project.all_pipelines.total_duration |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   def test_report |   def test_report | ||||||
|     respond_to do |format| |     respond_to do |format| | ||||||
|       format.html do |       format.html do | ||||||
|  |  | ||||||
|  | @ -20,7 +20,7 @@ module Mutations | ||||||
|                description: copy_field_description(Types::MergeRequestType, :description) |                description: copy_field_description(Types::MergeRequestType, :description) | ||||||
| 
 | 
 | ||||||
|       def resolve(args) |       def resolve(args) | ||||||
|         merge_request = authorized_find!(args.slice(:project_path, :iid)) |         merge_request = authorized_find!(**args.slice(:project_path, :iid)) | ||||||
|         attributes = args.slice(:title, :description, :target_branch).compact |         attributes = args.slice(:title, :description, :target_branch).compact | ||||||
| 
 | 
 | ||||||
|         ::MergeRequests::UpdateService |         ::MergeRequests::UpdateService | ||||||
|  |  | ||||||
|  | @ -111,6 +111,10 @@ class DiffNote < Note | ||||||
|     super.merge(suggestions_filter_enabled: true) |     super.merge(suggestions_filter_enabled: true) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |   def multiline? | ||||||
|  |     position&.multiline? | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|   private |   private | ||||||
| 
 | 
 | ||||||
|   def enqueue_diff_file_creation_job |   def enqueue_diff_file_creation_job | ||||||
|  |  | ||||||
|  | @ -122,7 +122,7 @@ module Notes | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def track_note_creation_usage_for_merge_requests(note) |     def track_note_creation_usage_for_merge_requests(note) | ||||||
|       Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_create_comment_action(user: note.author) |       Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_create_comment_action(note: note) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ module Notes | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def track_note_removal_usage_for_merge_requests(note) |     def track_note_removal_usage_for_merge_requests(note) | ||||||
|       Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_remove_comment_action(user: note.author) |       Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_remove_comment_action(note: note) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -98,7 +98,7 @@ module Notes | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def track_note_edit_usage_for_merge_requests(note) |     def track_note_edit_usage_for_merge_requests(note) | ||||||
|       Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_edit_comment_action(user: note.author) |       Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter.track_edit_comment_action(note: note) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -57,6 +57,8 @@ | ||||||
|               = render "projects/merge_requests/awards_block" |               = render "projects/merge_requests/awards_block" | ||||||
|               - if mr_action === "show" |               - if mr_action === "show" | ||||||
|                 - add_page_startup_api_call discussions_path(@merge_request) |                 - add_page_startup_api_call discussions_path(@merge_request) | ||||||
|  |                 - add_page_startup_api_call widget_project_json_merge_request_path(@project, @merge_request, format: :json) | ||||||
|  |                 - add_page_startup_api_call cached_widget_project_json_merge_request_path(@project, @merge_request, format: :json) | ||||||
|               #js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request).to_json, |               #js-vue-mr-discussions{ data: { notes_data: notes_data(@merge_request).to_json, | ||||||
|                 noteable_data: serialize_issuable(@merge_request, serializer: 'noteable'), |                 noteable_data: serialize_issuable(@merge_request, serializer: 'noteable'), | ||||||
|                 noteable_type: 'MergeRequest', |                 noteable_type: 'MergeRequest', | ||||||
|  |  | ||||||
|  | @ -1,13 +1,4 @@ | ||||||
| - page_title _('CI / CD Analytics') | - page_title _('CI / CD Analytics') | ||||||
| 
 | 
 | ||||||
| - if Feature.enabled?(:graphql_pipeline_analytics) |  | ||||||
| #js-project-pipelines-charts-app{ data: { project_path: @project.full_path, | #js-project-pipelines-charts-app{ data: { project_path: @project.full_path, | ||||||
|   should_render_deployment_frequency_charts: should_render_deployment_frequency_charts.to_s } } |   should_render_deployment_frequency_charts: should_render_deployment_frequency_charts.to_s } } | ||||||
| - else |  | ||||||
|   #js-project-pipelines-charts-app{ data: { counts: @counts, success_ratio: success_ratio(@counts), |  | ||||||
|     times_chart: { labels: @charts[:pipeline_times].labels, values: @charts[:pipeline_times].pipeline_times }, |  | ||||||
|     last_week_chart: { labels: @charts[:week].labels, totals: @charts[:week].total, success: @charts[:week].success }, |  | ||||||
|     last_month_chart: { labels: @charts[:month].labels, totals: @charts[:month].total, success: @charts[:month].success }, |  | ||||||
|     last_year_chart: { labels: @charts[:year].labels, totals: @charts[:year].total, success: @charts[:year].success }, |  | ||||||
|     project_path: @project.full_path, |  | ||||||
|     should_render_deployment_frequency_charts: should_render_deployment_frequency_charts.to_s } } |  | ||||||
|  |  | ||||||
|  | @ -1,2 +1,2 @@ | ||||||
| = link_to project_settings_operations_path(@project), title: _('Configure Tracing'), class: 'btn btn-success' do | = link_to project_settings_operations_path(@project), title: _('Configure Tracing'), class: 'gl-button btn btn-success' do | ||||||
|   = _('Add Jaeger URL') |   = _('Add Jaeger URL') | ||||||
|  |  | ||||||
|  | @ -23,6 +23,6 @@ | ||||||
|         .clearable-input |         .clearable-input | ||||||
|           = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' |           = text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date' | ||||||
|           = sprite_icon('close', size: 16, css_class: 'clear-icon js-clear-input gl-text-gray-200') |           = sprite_icon('close', size: 16, css_class: 'clear-icon js-clear-input gl-text-gray-200') | ||||||
|       = submit_tag _("Invite"), class: "btn btn-success", data: { qa_selector: 'invite_member_button' } |       = submit_tag _("Invite"), class: "gl-button btn btn-success", data: { qa_selector: 'invite_member_button' } | ||||||
|       - if can_import_members |       - if can_import_members | ||||||
|         = link_to _("Import"), import_path, class: "btn btn-default", title: _("Import members from another project") |         = link_to _("Import"), import_path, class: "gl-button btn btn-default", title: _("Import members from another project") | ||||||
|  |  | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Add metrics to creating, editing or removing multiline comments on merge requests | ||||||
|  | merge_request: 51098 | ||||||
|  | author: | ||||||
|  | type: other | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Use GlBadge for badges in pipeline_url.vue | ||||||
|  | merge_request: 51058 | ||||||
|  | author: Kev @KevSlashNull | ||||||
|  | type: changed | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Update pipeline graphs on CI/CD Analytics page to use GraphQL endpoint | ||||||
|  | merge_request: 51504 | ||||||
|  | author: | ||||||
|  | type: changed | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Add delete metric image REST API endpoint | ||||||
|  | merge_request: 50043 | ||||||
|  | author: | ||||||
|  | type: added | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Add gl-button to Add Jaeger URL | ||||||
|  | merge_request: 51553 | ||||||
|  | author: Yogi (@yo) | ||||||
|  | type: other | ||||||
|  | @ -0,0 +1,5 @@ | ||||||
|  | --- | ||||||
|  | title: Update to new GitLab UI button in members invite page | ||||||
|  | merge_request: 51300 | ||||||
|  | author: Yogi (@yo) | ||||||
|  | type: other | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| --- |  | ||||||
| name: graphql_pipeline_analytics |  | ||||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/48267 |  | ||||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/290153 |  | ||||||
| milestone: '13.7' |  | ||||||
| type: development |  | ||||||
| group: group::continuos integration |  | ||||||
| default_enabled: false |  | ||||||
|  | @ -4,5 +4,5 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/44622 | ||||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267162 | rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267162 | ||||||
| milestone: '13.5' | milestone: '13.5' | ||||||
| type: development | type: development | ||||||
| group: group::knowledge | group: group::editor | ||||||
| default_enabled: true | default_enabled: true | ||||||
|  |  | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | --- | ||||||
|  | name: usage_data_i_code_review_user_create_multiline_mr_comment | ||||||
|  | introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51098 | ||||||
|  | rollout_issue_url:  | ||||||
|  | milestone: '13.8' | ||||||
|  | type: development | ||||||
|  | group: group::code review | ||||||
|  | default_enabled: true | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | --- | ||||||
|  | name: usage_data_i_code_review_user_edit_multiline_mr_comment | ||||||
|  | introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51098 | ||||||
|  | rollout_issue_url:  | ||||||
|  | milestone: '13.8' | ||||||
|  | type: development | ||||||
|  | group: group::code review | ||||||
|  | default_enabled: true | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | --- | ||||||
|  | name: usage_data_i_code_review_user_remove_multiline_mr_comment | ||||||
|  | introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51098 | ||||||
|  | rollout_issue_url:  | ||||||
|  | milestone: '13.8' | ||||||
|  | type: development | ||||||
|  | group: group::code review | ||||||
|  | default_enabled: true | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | --- | ||||||
|  | name: usage_data_i_testing_full_code_quality_report_total | ||||||
|  | introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49079 | ||||||
|  | rollout_issue_url: | ||||||
|  | milestone: '13.8' | ||||||
|  | type: development | ||||||
|  | group: group::testing | ||||||
|  | default_enabled: true | ||||||
|  | @ -4,5 +4,5 @@ introduced_by_url: | ||||||
| rollout_issue_url:  | rollout_issue_url:  | ||||||
| milestone:  | milestone:  | ||||||
| type: development | type: development | ||||||
| group:  | group: group::editor | ||||||
| default_enabled: true | default_enabled: true | ||||||
|  |  | ||||||
|  | @ -1,8 +1,8 @@ | ||||||
| --- | --- | ||||||
| name: wiki_front_matter | name: wiki_front_matter | ||||||
| introduced_by_url:  | introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/27706 | ||||||
| rollout_issue_url:  | rollout_issue_url:  | ||||||
| milestone:  | milestone: '12.10' | ||||||
| type: development | type: development | ||||||
| group:  | group: group::editor | ||||||
| default_enabled: false | default_enabled: false | ||||||
|  |  | ||||||
|  | @ -226,10 +226,10 @@ class BackportEnterpriseSchema < ActiveRecord::Migration[5.0] | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def create_table_if_not_exists(name, *args, &block) |   def create_table_if_not_exists(name, **args, &block) | ||||||
|     return if table_exists?(name) |     return if table_exists?(name) | ||||||
| 
 | 
 | ||||||
|     create_table(name, *args, &block) |     create_table(name, **args, &block) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def add_concurrent_foreign_key(source, target, column:, on_delete: nil, name: nil) |   def add_concurrent_foreign_key(source, target, column:, on_delete: nil, name: nil) | ||||||
|  |  | ||||||
|  | @ -94,6 +94,7 @@ crosslinking | ||||||
| crosslinks | crosslinks | ||||||
| Crossplane | Crossplane | ||||||
| CrowdIn | CrowdIn | ||||||
|  | CSV | ||||||
| Dangerfile | Dangerfile | ||||||
| datetime | datetime | ||||||
| Debian | Debian | ||||||
|  |  | ||||||
|  | @ -2211,3 +2211,26 @@ Example response: | ||||||
|     } |     } | ||||||
| ] | ] | ||||||
| ``` | ``` | ||||||
|  | 
 | ||||||
|  | ## Delete metric image | ||||||
|  | 
 | ||||||
|  | Available only for Incident issues. | ||||||
|  | 
 | ||||||
|  | ```plaintext | ||||||
|  | DELETE /projects/:id/issues/:issue_iid/metric_images/:image_id | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | | Attribute   | Type    | Required | Description                          | | ||||||
|  | |-------------|---------|----------|--------------------------------------| | ||||||
|  | | `id`        | integer/string | yes      | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user  | | ||||||
|  | | `issue_iid` | integer | yes      | The internal ID of a project's issue | | ||||||
|  | | `image_id` | integer | yes      | The ID of the image | | ||||||
|  | 
 | ||||||
|  | ```shell | ||||||
|  | curl --header "PRIVATE-TOKEN: <your_access_token>" --request DELETE "https://gitlab.example.com/api/v4/projects/5/issues/93/metric_images/1" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Can return the following status codes: | ||||||
|  | 
 | ||||||
|  | - `204 No Content`, if the image was deleted successfully. | ||||||
|  | - `400 Bad Request`, if the image could not be deleted. | ||||||
|  |  | ||||||
|  | @ -269,6 +269,15 @@ by a project that has jobs with a long timeout (for example, one week). | ||||||
| 
 | 
 | ||||||
| When not configured, runners do not override the project timeout. | When not configured, runners do not override the project timeout. | ||||||
| 
 | 
 | ||||||
|  | On GitLab.com, you cannot override the job timeout for shared runners and must use the [project defined timeout](../pipelines/settings.md#timeout). | ||||||
|  | 
 | ||||||
|  | To set the maximum job timeout: | ||||||
|  | 
 | ||||||
|  | 1. In a project, go to **Settings > CI/CD > Runners**. | ||||||
|  | 1. Select your specific runner to edit the settings.  | ||||||
|  | 1. Enter a value under **Maximum job timeout**. | ||||||
|  | 1. Select **Save changes**. | ||||||
|  | 
 | ||||||
| How this feature works: | How this feature works: | ||||||
| 
 | 
 | ||||||
| **Example 1 - Runner timeout bigger than project timeout** | **Example 1 - Runner timeout bigger than project timeout** | ||||||
|  |  | ||||||
|  | @ -529,11 +529,11 @@ You can use these fake tokens as examples: | ||||||
| | Usage                 | Guidance | | | Usage                 | Guidance | | ||||||
| |-----------------------|----------| | |-----------------------|----------| | ||||||
| | above                 | Try to avoid extra words when referring to an example or table in a documentation page, but if required, use **previously** instead. | | | above                 | Try to avoid extra words when referring to an example or table in a documentation page, but if required, use **previously** instead. | | ||||||
| | admin, admin area     | Use **administration**, **administrator**, **administer**, or **Admin Area** instead. | | | admin, admin area     | Use **administration**, **administrator**, **administer**, or **Admin Area** instead. ([Vale](../testing.md#vale) rule: [`Admin.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/Admin.yml)) | | ||||||
| | allow, enable         | Try to avoid, unless you are talking about security-related features. For example, instead of "This feature allows you to create a pipeline," use "Use this feature to create a pipeline." This phrasing is more active and is from the user perspective, rather than the person who implemented the feature. [View details](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/a/allow-allows). | | | allow, enable         | Try to avoid, unless you are talking about security-related features. For example, instead of "This feature allows you to create a pipeline," use "Use this feature to create a pipeline." This phrasing is more active and is from the user perspective, rather than the person who implemented the feature. [View details](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/a/allow-allows). | | ||||||
| | and/or                | Use **or** instead, or another sensible construction. | | | and/or                | Use **or** instead, or another sensible construction. | | ||||||
| | below                 | Try to avoid extra words when referring to an example or table in a documentation page, but if required, use **following** instead. | | | below                 | Try to avoid extra words when referring to an example or table in a documentation page, but if required, use **following** instead. | | ||||||
| | currently             | Do not use when talking about the product or its features. The documentation describes the product as it is today. | | | currently             | Do not use when talking about the product or its features. The documentation describes the product as it is today. ([Vale](../testing.md#vale) rule: [`CurrentStatus.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/CurrentStatus.yml)) | | ||||||
| | easily                | Do not use. If the user doesn't find the process to be these things, we lose their trust. | | | easily                | Do not use. If the user doesn't find the process to be these things, we lose their trust. | | ||||||
| | e.g.                  | Do not use Latin abbreviations. Use **for example**, **such as**, **for instance**, or **like** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) | | | e.g.                  | Do not use Latin abbreviations. Use **for example**, **such as**, **for instance**, or **like** instead. ([Vale](../testing.md#vale) rule: [`LatinTerms.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/LatinTerms.yml)) | | ||||||
| | future tense          | When possible, use present tense instead. For example, use `after you execute this command, GitLab displays the result` instead of `after you execute this command, GitLab will display the result`. ([Vale](../testing.md#vale) rule: [`FutureTense.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FutureTense.yml)) | | | future tense          | When possible, use present tense instead. For example, use `after you execute this command, GitLab displays the result` instead of `after you execute this command, GitLab will display the result`. ([Vale](../testing.md#vale) rule: [`FutureTense.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/FutureTense.yml)) | | ||||||
|  |  | ||||||
|  | @ -61,6 +61,7 @@ including: | ||||||
| - Container Registry images | - Container Registry images | ||||||
| - GitLab Pages content | - GitLab Pages content | ||||||
| - Snippets | - Snippets | ||||||
|  | - Group wikis **(PREMIUM)** | ||||||
| 
 | 
 | ||||||
| WARNING: | WARNING: | ||||||
| GitLab does not back up any configuration files, SSL certificates, or system | GitLab does not back up any configuration files, SSL certificates, or system | ||||||
|  |  | ||||||
|  | @ -68,6 +68,10 @@ the official analyzers. | ||||||
| 
 | 
 | ||||||
| ### Selecting specific analyzers | ### Selecting specific analyzers | ||||||
| 
 | 
 | ||||||
|  | WARNING: | ||||||
|  | `SAST_DEFAULT_ANALYZERS` is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50872) in GitLab 13.8, | ||||||
|  | and is scheduled for [removal in GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/290777). | ||||||
|  | 
 | ||||||
| You can select the official analyzers you want to run. Here's how to enable | You can select the official analyzers you want to run. Here's how to enable | ||||||
| `bandit` and `flawfinder` while disabling all the other default ones. | `bandit` and `flawfinder` while disabling all the other default ones. | ||||||
| In `.gitlab-ci.yml` define: | In `.gitlab-ci.yml` define: | ||||||
|  | @ -83,9 +87,9 @@ variables: | ||||||
| `bandit` runs first. When merging the reports, SAST | `bandit` runs first. When merging the reports, SAST | ||||||
| removes the duplicates and keeps the `bandit` entries. | removes the duplicates and keeps the `bandit` entries. | ||||||
| 
 | 
 | ||||||
| ### Disabling default analyzers | ### Disabling all default analyzers | ||||||
| 
 | 
 | ||||||
| Setting `SAST_DEFAULT_ANALYZERS` to an empty string disables all the official | Setting `SAST_DISABLED` to `true` disables all the official | ||||||
| default analyzers. In `.gitlab-ci.yml` define: | default analyzers. In `.gitlab-ci.yml` define: | ||||||
| 
 | 
 | ||||||
| ```yaml | ```yaml | ||||||
|  | @ -93,11 +97,25 @@ include: | ||||||
|   - template: Security/SAST.gitlab-ci.yml |   - template: Security/SAST.gitlab-ci.yml | ||||||
| 
 | 
 | ||||||
| variables: | variables: | ||||||
|   SAST_DEFAULT_ANALYZERS: "" |   SAST_DISABLED: true | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| That's needed when one totally relies on [custom analyzers](#custom-analyzers). | That's needed when one totally relies on [custom analyzers](#custom-analyzers). | ||||||
| 
 | 
 | ||||||
|  | ### Disabling specific default analyzers | ||||||
|  | 
 | ||||||
|  | Set `SAST_EXCLUDED_ANALYZERS` to a comma-delimited string that includes the official | ||||||
|  | default analyzers that you want to avoid running. In `.gitlab-ci.yml` define the | ||||||
|  | following to prevent the `eslint` analyzer from running: | ||||||
|  | 
 | ||||||
|  | ```yaml | ||||||
|  | include: | ||||||
|  |   - template: Security/SAST.gitlab-ci.yml | ||||||
|  | 
 | ||||||
|  | variables: | ||||||
|  |   SAST_EXCLUDED_ANALYZERS: "eslint" | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ## Custom Analyzers | ## Custom Analyzers | ||||||
| 
 | 
 | ||||||
| You can provide your own analyzers by | You can provide your own analyzers by | ||||||
|  |  | ||||||
|  | @ -431,7 +431,8 @@ The following are Docker image-related variables. | ||||||
| |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------| | |---------------------------|---------------------------------------------------------------------------------------------------------------------------------------| | ||||||
| | `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the default images (proxy). Read more about [customizing analyzers](analyzers.md). | | | `SECURE_ANALYZERS_PREFIX` | Override the name of the Docker registry providing the default images (proxy). Read more about [customizing analyzers](analyzers.md). | | ||||||
| | `SAST_ANALYZER_IMAGE_TAG` | **DEPRECATED:** Override the Docker tag of the default images. Read more about [customizing analyzers](analyzers.md).                 | | | `SAST_ANALYZER_IMAGE_TAG` | **DEPRECATED:** Override the Docker tag of the default images. Read more about [customizing analyzers](analyzers.md).                 | | ||||||
| | `SAST_DEFAULT_ANALYZERS`  | Override the names of default images. Read more about [customizing analyzers](analyzers.md).                                          | | | `SAST_DEFAULT_ANALYZERS`  | **DEPRECATED:** Override the names of default images. Scheduled for [removal in GitLab 14.0](https://gitlab.com/gitlab-org/gitlab/-/issues/290777).                          | | ||||||
|  | | `SAST_EXCLUDED_ANALYZERS` | Names of default images that should never run. Read more about [customizing analyzers](analyzers.md).                                 | | ||||||
| 
 | 
 | ||||||
| #### Vulnerability filters | #### Vulnerability filters | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -53,6 +53,7 @@ The [default ruleset provided by Gitleaks](https://gitlab.com/gitlab-org/securit | ||||||
|   - Twitter API |   - Twitter API | ||||||
| - Cloud SaaS vendors: | - Cloud SaaS vendors: | ||||||
|   - GitHub API |   - GitHub API | ||||||
|  |   - Shopify API | ||||||
|   - Slack Token |   - Slack Token | ||||||
|   - Slack Webhook |   - Slack Webhook | ||||||
|   - Stripe API |   - Stripe API | ||||||
|  |  | ||||||
|  | @ -460,7 +460,7 @@ and above. | ||||||
| There are a few limitations compared to project wikis: | There are a few limitations compared to project wikis: | ||||||
| 
 | 
 | ||||||
| - Git LFS is not supported. | - Git LFS is not supported. | ||||||
| - Group wikis are not included in global search, group exports, backups, and Geo replication. | - Group wikis are not included in global search, group exports, and Geo replication. | ||||||
| - Changes to group wikis don't show up in the group's activity feed. | - Changes to group wikis don't show up in the group's activity feed. | ||||||
| - Group wikis [can't be moved](../../api/project_repository_storage_moves.md#limitations) using the project | - Group wikis [can't be moved](../../api/project_repository_storage_moves.md#limitations) using the project | ||||||
|   repository moves API. |   repository moves API. | ||||||
|  |  | ||||||
|  | @ -272,6 +272,6 @@ Output indicates that the package has been successfully installed. | ||||||
| 
 | 
 | ||||||
| WARNING: | WARNING: | ||||||
| Never commit the `auth.json` file to your repository. To install packages from a CI/CD job, | Never commit the `auth.json` file to your repository. To install packages from a CI/CD job, | ||||||
| consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages-with-satis.md#authentication) tool with your personal access token | consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages.md#satis) tool with your personal access token | ||||||
| stored in a [GitLab CI/CD environment variable](../../../ci/variables/README.md) or in | stored in a [GitLab CI/CD environment variable](../../../ci/variables/README.md) or in | ||||||
| [HashiCorp Vault](../../../ci/secrets/index.md). | [HashiCorp Vault](../../../ci/secrets/index.md). | ||||||
|  |  | ||||||
|  | @ -95,7 +95,7 @@ The following table depicts the various user permission levels in a project. | ||||||
| | View metrics dashboard annotations                |         | ✓          | ✓           | ✓        | ✓      | | | View metrics dashboard annotations                |         | ✓          | ✓           | ✓        | ✓      | | ||||||
| | Archive/reopen requirements **(ULTIMATE)**        |         | ✓          | ✓           | ✓        | ✓      | | | Archive/reopen requirements **(ULTIMATE)**        |         | ✓          | ✓           | ✓        | ✓      | | ||||||
| | Create/edit requirements **(ULTIMATE)**           |         | ✓          | ✓           | ✓        | ✓      | | | Create/edit requirements **(ULTIMATE)**           |         | ✓          | ✓           | ✓        | ✓      | | ||||||
| | Import requirements **(ULTIMATE)**                |         | ✓          | ✓           | ✓        | ✓      | | | Import/export requirements **(ULTIMATE)**         |         | ✓          | ✓           | ✓        | ✓      | | ||||||
| | Create new [test case](../ci/test_cases/index.md) |         | ✓          | ✓           | ✓        | ✓      | | | Create new [test case](../ci/test_cases/index.md) |         | ✓          | ✓           | ✓        | ✓      | | ||||||
| | Archive [test case](../ci/test_cases/index.md)    |         | ✓          | ✓           | ✓        | ✓      | | | Archive [test case](../ci/test_cases/index.md)    |         | ✓          | ✓           | ✓        | ✓      | | ||||||
| | Move [test case](../ci/test_cases/index.md)       |         | ✓          | ✓           | ✓        | ✓      | | | Move [test case](../ci/test_cases/index.md)       |         | ✓          | ✓           | ✓        | ✓      | | ||||||
|  |  | ||||||
|  | @ -225,6 +225,52 @@ the rules for "Groups" and "Documentation" sections: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
|  | #### Optional Code Owners Sections **(PREMIUM)** | ||||||
|  | 
 | ||||||
|  | > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/232995) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.8 behind a feature flag, enabled by default. | ||||||
|  | 
 | ||||||
|  | When you want to make a certain section optional, you can do so by adding a code owners section prepended with the caret `^` character. Approvals from owners listed in the section will **not** be required. For example: | ||||||
|  | 
 | ||||||
|  | ```plaintext | ||||||
|  | [Documentation] | ||||||
|  | *.md @root | ||||||
|  | 
 | ||||||
|  | [Ruby] | ||||||
|  | *.rb @root | ||||||
|  | 
 | ||||||
|  | ^[Go] | ||||||
|  | *.go @root | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | The optional code owners section will be displayed in merge requests under the **Approval Rules** area: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | If a section is duplicated in the file, and one of them is marked as optional and the other isn't, the requirement prevails.  | ||||||
|  | 
 | ||||||
|  | For example, the code owners of the "Documentation" section below will still be required to approve merge requests:  | ||||||
|  | 
 | ||||||
|  | ```plaintext | ||||||
|  | [Documentation] | ||||||
|  | *.md @root | ||||||
|  | 
 | ||||||
|  | [Ruby] | ||||||
|  | *.rb @root | ||||||
|  | 
 | ||||||
|  | ^[Go] | ||||||
|  | *.go @root | ||||||
|  | 
 | ||||||
|  | ^[Documentation] | ||||||
|  | *.txt @root | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | Optional sections in the code owners file are currently treated as optional only | ||||||
|  | when changes are submitted via merge requests. If a change is submitted directly | ||||||
|  | to the protected branch, approval from code owners will still be required, even if the | ||||||
|  | section is marked as optional. We plan to change this in a | ||||||
|  | [future release](https://gitlab.com/gitlab-org/gitlab/-/issues/297638), | ||||||
|  | where direct pushes to the protected branch will be allowed for sections marked as optional. | ||||||
|  | 
 | ||||||
| ## Example `CODEOWNERS` file | ## Example `CODEOWNERS` file | ||||||
| 
 | 
 | ||||||
| ```plaintext | ```plaintext | ||||||
|  |  | ||||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 102 KiB | 
|  | @ -179,7 +179,8 @@ for the issue. Notifications are automatically enabled after you participate in | ||||||
| 
 | 
 | ||||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18816) in GitLab 13.8. | > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18816) in GitLab 13.8. | ||||||
| 
 | 
 | ||||||
| Guest users can see a button to copy the email address for the issue. Sending an email to this address creates a comment containing the email body. | Guest users can see a button in the right sidebar to copy the email address for the issue. | ||||||
|  | Sending an email to this address creates a comment containing the email body. | ||||||
| 
 | 
 | ||||||
| ### Edit | ### Edit | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -62,7 +62,7 @@ request's page at the top-right side: | ||||||
| - Enable the [squash commits when merge request is accepted](squash_and_merge.md) option to combine all the commits into one before merging, thus keep a clean commit history in your repository. | - Enable the [squash commits when merge request is accepted](squash_and_merge.md) option to combine all the commits into one before merging, thus keep a clean commit history in your repository. | ||||||
| - Set the merge request as a [**Draft**](work_in_progress_merge_requests.md) to avoid accidental merges before it is ready. | - Set the merge request as a [**Draft**](work_in_progress_merge_requests.md) to avoid accidental merges before it is ready. | ||||||
| 
 | 
 | ||||||
| Once you have created the merge request, you can also: | After you have created the merge request, you can also: | ||||||
| 
 | 
 | ||||||
| - [Discuss](../../discussions/index.md) your implementation with your team in the merge request thread. | - [Discuss](../../discussions/index.md) your implementation with your team in the merge request thread. | ||||||
| - [Perform inline code reviews](reviewing_and_managing_merge_requests.md#perform-inline-code-reviews). | - [Perform inline code reviews](reviewing_and_managing_merge_requests.md#perform-inline-code-reviews). | ||||||
|  | @ -70,7 +70,7 @@ Once you have created the merge request, you can also: | ||||||
| - Preview continuous integration [pipelines on the merge request widget](reviewing_and_managing_merge_requests.md#pipeline-status-in-merge-requests-widgets). | - Preview continuous integration [pipelines on the merge request widget](reviewing_and_managing_merge_requests.md#pipeline-status-in-merge-requests-widgets). | ||||||
| - Preview how your changes look directly on your deployed application with [Review Apps](reviewing_and_managing_merge_requests.md#live-preview-with-review-apps). | - Preview how your changes look directly on your deployed application with [Review Apps](reviewing_and_managing_merge_requests.md#live-preview-with-review-apps). | ||||||
| - [Allow collaboration on merge requests across forks](allow_collaboration.md). | - [Allow collaboration on merge requests across forks](allow_collaboration.md). | ||||||
| - Perform a [Review](../../discussions/index.md#merge-request-reviews) in order to create multiple comments on a diff and publish them once you're ready. | - Perform a [Review](../../discussions/index.md#merge-request-reviews) to create multiple comments on a diff and publish them when you're ready. | ||||||
| - Add [code suggestions](../../discussions/index.md#suggest-changes) to change the content of merge requests directly into merge request threads, and easily apply them to the codebase directly from the UI. | - Add [code suggestions](../../discussions/index.md#suggest-changes) to change the content of merge requests directly into merge request threads, and easily apply them to the codebase directly from the UI. | ||||||
| - Add a time estimation and the time spent with that merge request with [Time Tracking](../time_tracking.md#time-tracking). | - Add a time estimation and the time spent with that merge request with [Time Tracking](../time_tracking.md#time-tracking). | ||||||
| 
 | 
 | ||||||
|  | @ -161,6 +161,53 @@ Feature.disable(:merge_request_reviewers) | ||||||
| Feature.disable(:merge_request_reviewers, Project.find(<project id>)) | Feature.disable(:merge_request_reviewers, Project.find(<project id>)) | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
|  | #### Reviewer approval rules | ||||||
|  | 
 | ||||||
|  | > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233736) in GitLab 13.8. | ||||||
|  | > - It was [deployed behind a feature flag](../../../user/feature_flags.md), disabled by default. | ||||||
|  | > - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51183) in GitLab 13.8. | ||||||
|  | > - It's enabled on GitLab.com. | ||||||
|  | > - It's recommended for production use. | ||||||
|  | > - It can be enabled or disabled for a single project. | ||||||
|  | > - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-reviewer-approval-rules). **(CORE ONLY)** | ||||||
|  | 
 | ||||||
|  | When editing the **Reviewers** field in a new or existing merge request, this feature | ||||||
|  | displays the name of the matching [approval rule](merge_request_approvals.md#approval-rules) | ||||||
|  | below the name of each suggested reviewer. [Code Owners](../code_owners.md) are displayed as `Codeowner` without group detail. We intend to iterate on this feature in future releases. | ||||||
|  | 
 | ||||||
|  | This example shows reviewers and approval rules when creating a new merge request: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | This example shows reviewers and approval rules in a merge request sidebar: | ||||||
|  | 
 | ||||||
|  |  | ||||||
|  | 
 | ||||||
|  | ##### Enable or disable Reviewer Approval Rules **(CORE ONLY)** | ||||||
|  | 
 | ||||||
|  | Merge Request Reviewers is under development and ready for production use. | ||||||
|  | It is deployed behind a feature flag that is **enabled by default**. | ||||||
|  | [GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) | ||||||
|  | can opt to disable it. | ||||||
|  | 
 | ||||||
|  | To enable it: | ||||||
|  | 
 | ||||||
|  | ```ruby | ||||||
|  | # For the instance | ||||||
|  | Feature.enable(:reviewer_approval_rules) | ||||||
|  | # For a single project | ||||||
|  | Feature.enable(:reviewer_approval_rules, Project.find(<project id>)) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
|  | To disable it: | ||||||
|  | 
 | ||||||
|  | ```ruby | ||||||
|  | # For the instance | ||||||
|  | Feature.disable(:reviewer_approval_rules) | ||||||
|  | # For a single project | ||||||
|  | Feature.disable(:reviewer_approval_rules, Project.find(<project id>)) | ||||||
|  | ``` | ||||||
|  | 
 | ||||||
| ### Merge requests to close issues | ### Merge requests to close issues | ||||||
| 
 | 
 | ||||||
| If the merge request is being created to resolve an issue, you can | If the merge request is being created to resolve an issue, you can | ||||||
|  | @ -200,5 +247,5 @@ is set for deletion, the merge request widget displays the | ||||||
|   at once. By doing so, you save pipeline minutes. |   at once. By doing so, you save pipeline minutes. | ||||||
| - Delete feature branches on merge or after merging them to keep your repository clean. | - Delete feature branches on merge or after merging them to keep your repository clean. | ||||||
| - Take one thing at a time and ship the smallest changes possible. By doing so, | - Take one thing at a time and ship the smallest changes possible. By doing so, | ||||||
|   you'll have faster reviews and your changes will be less prone to errors. |   reviews are faster and your changes are less prone to errors. | ||||||
| - Do not use capital letters nor special chars in branch names. | - Do not use capital letters nor special chars in branch names. | ||||||
|  |  | ||||||
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 41 KiB | 
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 38 KiB | 
|  | @ -34,7 +34,7 @@ Users with Reporter or higher [permissions](../../permissions.md) can create req | ||||||
| 
 | 
 | ||||||
| To create a requirement: | To create a requirement: | ||||||
| 
 | 
 | ||||||
| 1. From your project page, go to **Requirements**. | 1. In a project, go to **Requirements**. | ||||||
| 1. Select **New requirement**. | 1. Select **New requirement**. | ||||||
| 1. Enter a title and description and select **Create requirement**. | 1. Enter a title and description and select **Create requirement**. | ||||||
| 
 | 
 | ||||||
|  | @ -200,10 +200,10 @@ requirements_confirmation: | ||||||
| 
 | 
 | ||||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/246857) in GitLab 13.7. | > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/246857) in GitLab 13.7. | ||||||
| 
 | 
 | ||||||
| You can import requirements to a project by uploading a CSV file with the columns | You can import requirements to a project by uploading a [CSV file](https://en.wikipedia.org/wiki/Comma-separated_values) | ||||||
| `title` and `description`. | with the columns `title` and `description`. | ||||||
| 
 | 
 | ||||||
| The user uploading the CSV file will be set as the author of the imported requirements. | After the import, the user uploading the CSV file is set as the author of the imported requirements. | ||||||
| 
 | 
 | ||||||
| Users with Reporter or higher [permissions](../../permissions.md) can import requirements. | Users with Reporter or higher [permissions](../../permissions.md) can import requirements. | ||||||
| 
 | 
 | ||||||
|  | @ -213,20 +213,20 @@ Before you import your file: | ||||||
| 
 | 
 | ||||||
| - Consider importing a test file containing only a few requirements. There is no way to undo a large | - Consider importing a test file containing only a few requirements. There is no way to undo a large | ||||||
|   import without using the GitLab API. |   import without using the GitLab API. | ||||||
| - Ensure your CSV file meets the [file format](#csv-file-format) requirements. | - Ensure your CSV file meets the [file format](#imported-csv-file-format) requirements. | ||||||
| 
 | 
 | ||||||
| To import requirements: | To import requirements: | ||||||
| 
 | 
 | ||||||
| 1. Navigate to a project's Requirements page. | 1. In a project, go to **Requirements**. | ||||||
|    - If the project already has existing requirements, click the import icon (**{import}**) at the |    - If the project already has existing requirements, select the import icon (**{import}**) in the | ||||||
|      top right. |      top right. | ||||||
|    - For a project without any requirements, click **Import CSV** in the middle of the page. |    - For a project without any requirements, select **Import CSV** in the middle of the page. | ||||||
| 1. Select the file and click **Import requirements**. | 1. Select the file and select **Import requirements**. | ||||||
| 
 | 
 | ||||||
| The file is processed in the background and a notification email is sent | The file is processed in the background and a notification email is sent | ||||||
| to you after the import is complete. | to you after the import is complete. | ||||||
| 
 | 
 | ||||||
| ### CSV file format | ### Imported CSV file format | ||||||
| 
 | 
 | ||||||
| When importing requirements from a CSV file, it must be formatted in a certain way: | When importing requirements from a CSV file, it must be formatted in a certain way: | ||||||
| 
 | 
 | ||||||
|  | @ -257,3 +257,37 @@ Another Title,"A description, with a comma" | ||||||
| The limit depends on the configuration value of Max Attachment Size for the GitLab instance. | The limit depends on the configuration value of Max Attachment Size for the GitLab instance. | ||||||
| 
 | 
 | ||||||
| For GitLab.com, it is set to 10 MB. | For GitLab.com, it is set to 10 MB. | ||||||
|  | 
 | ||||||
|  | ## Export requirements to a CSV file | ||||||
|  | 
 | ||||||
|  | > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/290813) in GitLab 13.8. | ||||||
|  | 
 | ||||||
|  | You can export GitLab requirements to a | ||||||
|  | [CSV file](https://en.wikipedia.org/wiki/Comma-separated_values) sent to your default notification | ||||||
|  | email as an attachment. | ||||||
|  | 
 | ||||||
|  | By exporting requirements, you and your team can import them into another tool or share them with | ||||||
|  | your customers. Exporting requirements can aid collaboration with higher-level systems, as well as | ||||||
|  | audit and regulatory compliance tasks. | ||||||
|  | 
 | ||||||
|  | Users with Reporter or higher [permissions](../../permissions.md) can export requirements. | ||||||
|  | 
 | ||||||
|  | To export requirements: | ||||||
|  | 
 | ||||||
|  | 1. In a project, go to **Requirements**. | ||||||
|  | 1. Select the **Export as CSV** icon (**{export}**) in the top right. A confirmation modal appears. | ||||||
|  | 1. Select **Export requirements**. The exported CSV file is sent to the email address associated with your user. | ||||||
|  | 
 | ||||||
|  | ### Exported CSV file format | ||||||
|  | 
 | ||||||
|  | You can preview the exported CSV file in a spreadsheet editor, such as Microsoft Excel, | ||||||
|  | OpenOffice Calc, or Google Sheets. | ||||||
|  | 
 | ||||||
|  | The exported CSV file contains the following columns: | ||||||
|  | 
 | ||||||
|  | - Requirement ID | ||||||
|  | - Title | ||||||
|  | - Description | ||||||
|  | - Author Username | ||||||
|  | - Latest Test Report State | ||||||
|  | - Latest Test Report Created At (UTC) | ||||||
|  |  | ||||||
|  | @ -40,31 +40,42 @@ module Backup | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def restore |     def restore | ||||||
|       Project.find_each(batch_size: 1000) do |project| |       restore_project_repositories | ||||||
|         restore_repository(project, Gitlab::GlRepository::PROJECT) |       restore_snippets | ||||||
|         restore_repository(project, Gitlab::GlRepository::WIKI) |  | ||||||
|         restore_repository(project, Gitlab::GlRepository::DESIGN) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       invalid_ids = Snippet.find_each(batch_size: 1000) |  | ||||||
|         .map { |snippet| restore_snippet_repository(snippet) } |  | ||||||
|         .compact |  | ||||||
| 
 |  | ||||||
|       cleanup_snippets_without_repositories(invalid_ids) |  | ||||||
| 
 | 
 | ||||||
|       restore_object_pools |       restore_object_pools | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     private |     private | ||||||
| 
 | 
 | ||||||
|  |     def restore_project_repositories | ||||||
|  |       Project.find_each(batch_size: 1000) do |project| | ||||||
|  |         restore_repository(project, Gitlab::GlRepository::PROJECT) | ||||||
|  |         restore_repository(project, Gitlab::GlRepository::WIKI) | ||||||
|  |         restore_repository(project, Gitlab::GlRepository::DESIGN) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     def restore_snippets | ||||||
|  |       invalid_ids = Snippet.find_each(batch_size: 1000) | ||||||
|  |         .map { |snippet| restore_snippet_repository(snippet) } | ||||||
|  |         .compact | ||||||
|  | 
 | ||||||
|  |       cleanup_snippets_without_repositories(invalid_ids) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     def check_valid_storages! |     def check_valid_storages! | ||||||
|       [ProjectRepository, SnippetRepository].each do |klass| |       repository_storage_klasses.each do |klass| | ||||||
|         if klass.excluding_repository_storage(Gitlab.config.repositories.storages.keys).exists? |         if klass.excluding_repository_storage(Gitlab.config.repositories.storages.keys).exists? | ||||||
|           raise Error, "repositories.storages in gitlab.yml does not include all storages used by #{klass}" |           raise Error, "repositories.storages in gitlab.yml does not include all storages used by #{klass}" | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     def repository_storage_klasses | ||||||
|  |       [ProjectRepository, SnippetRepository] | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     def backup_repos_path |     def backup_repos_path | ||||||
|       @backup_repos_path ||= File.join(Gitlab.config.backup.path, 'repositories') |       @backup_repos_path ||= File.join(Gitlab.config.backup.path, 'repositories') | ||||||
|     end |     end | ||||||
|  | @ -103,12 +114,7 @@ module Backup | ||||||
|               end |               end | ||||||
| 
 | 
 | ||||||
|               begin |               begin | ||||||
|                 case container |                 dump_container(container) | ||||||
|                 when Project |  | ||||||
|                   dump_project(container) |  | ||||||
|                 when Snippet |  | ||||||
|                   dump_snippet(container) |  | ||||||
|                 end |  | ||||||
|               rescue => e |               rescue => e | ||||||
|                 errors << e |                 errors << e | ||||||
|                 break |                 break | ||||||
|  | @ -130,6 +136,15 @@ module Backup | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     def dump_container(container) | ||||||
|  |       case container | ||||||
|  |       when Project | ||||||
|  |         dump_project(container) | ||||||
|  |       when Snippet | ||||||
|  |         dump_snippet(container) | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     def dump_project(project) |     def dump_project(project) | ||||||
|       backup_repository(project, Gitlab::GlRepository::PROJECT) |       backup_repository(project, Gitlab::GlRepository::PROJECT) | ||||||
|       backup_repository(project, Gitlab::GlRepository::WIKI) |       backup_repository(project, Gitlab::GlRepository::WIKI) | ||||||
|  | @ -308,3 +323,5 @@ module Backup | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| end | end | ||||||
|  | 
 | ||||||
|  | Backup::Repositories.prepend_if_ee('EE::Backup::Repositories') | ||||||
|  |  | ||||||
|  | @ -1165,9 +1165,9 @@ module Gitlab | ||||||
|         Arel::Nodes::SqlLiteral.new(replace.to_sql) |         Arel::Nodes::SqlLiteral.new(replace.to_sql) | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def remove_foreign_key_if_exists(*args) |       def remove_foreign_key_if_exists(...) | ||||||
|         if foreign_key_exists?(*args) |         if foreign_key_exists?(...) | ||||||
|           remove_foreign_key(*args) |           remove_foreign_key(...) | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -19,6 +19,7 @@ module Gitlab | ||||||
|                :height, |                :height, | ||||||
|                :x, |                :x, | ||||||
|                :y, |                :y, | ||||||
|  |                :line_range, | ||||||
|                :position_type, to: :formatter |                :position_type, to: :formatter | ||||||
| 
 | 
 | ||||||
|       # A position can belong to a text line or to an image coordinate |       # A position can belong to a text line or to an image coordinate | ||||||
|  | @ -167,6 +168,12 @@ module Gitlab | ||||||
|         end |         end | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|  |       def multiline? | ||||||
|  |         return unless on_text? && line_range | ||||||
|  | 
 | ||||||
|  |         line_range['start'] != line_range['end'] | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|       private |       private | ||||||
| 
 | 
 | ||||||
|       def find_diff_file(repository) |       def find_diff_file(repository) | ||||||
|  |  | ||||||
|  | @ -258,6 +258,11 @@ | ||||||
|   redis_slot: testing |   redis_slot: testing | ||||||
|   aggregation: weekly |   aggregation: weekly | ||||||
|   feature_flag: usage_data_i_testing_group_code_coverage_visit_total |   feature_flag: usage_data_i_testing_group_code_coverage_visit_total | ||||||
|  | - name: i_testing_full_code_quality_report_total | ||||||
|  |   category: testing | ||||||
|  |   redis_slot: testing | ||||||
|  |   aggregation: weekly | ||||||
|  |   feature_flag: usage_data_i_testing_full_code_quality_report_total | ||||||
| # Project Management group | # Project Management group | ||||||
| - name: g_project_management_issue_title_changed | - name: g_project_management_issue_title_changed | ||||||
|   category: issues_edit |   category: issues_edit | ||||||
|  | @ -496,6 +501,21 @@ | ||||||
|   category: code_review |   category: code_review | ||||||
|   aggregation: weekly |   aggregation: weekly | ||||||
|   feature_flag: usage_data_i_code_review_user_publish_review |   feature_flag: usage_data_i_code_review_user_publish_review | ||||||
|  | - name: i_code_review_user_create_multiline_mr_comment | ||||||
|  |   redis_slot: code_review | ||||||
|  |   category: code_review | ||||||
|  |   aggregation: weekly | ||||||
|  |   feature_flag: usage_data_i_code_review_user_create_multiline_mr_comment | ||||||
|  | - name: i_code_review_user_edit_multiline_mr_comment | ||||||
|  |   redis_slot: code_review | ||||||
|  |   category: code_review | ||||||
|  |   aggregation: weekly | ||||||
|  |   feature_flag: usage_data_i_code_review_user_edit_multiline_mr_comment | ||||||
|  | - name: i_code_review_user_remove_multiline_mr_comment | ||||||
|  |   redis_slot: code_review | ||||||
|  |   category: code_review | ||||||
|  |   aggregation: weekly | ||||||
|  |   feature_flag: usage_data_i_code_review_user_remove_multiline_mr_comment | ||||||
| # Terraform | # Terraform | ||||||
| - name: p_terraform_state_api_unique_users | - name: p_terraform_state_api_unique_users | ||||||
|   category: terraform |   category: terraform | ||||||
|  |  | ||||||
|  | @ -15,6 +15,9 @@ module Gitlab | ||||||
|       MR_REMOVE_COMMENT_ACTION = 'i_code_review_user_remove_mr_comment' |       MR_REMOVE_COMMENT_ACTION = 'i_code_review_user_remove_mr_comment' | ||||||
|       MR_CREATE_REVIEW_NOTE_ACTION = 'i_code_review_user_create_review_note' |       MR_CREATE_REVIEW_NOTE_ACTION = 'i_code_review_user_create_review_note' | ||||||
|       MR_PUBLISH_REVIEW_ACTION = 'i_code_review_user_publish_review' |       MR_PUBLISH_REVIEW_ACTION = 'i_code_review_user_publish_review' | ||||||
|  |       MR_CREATE_MULTILINE_COMMENT_ACTION = 'i_code_review_user_create_multiline_mr_comment' | ||||||
|  |       MR_EDIT_MULTILINE_COMMENT_ACTION = 'i_code_review_user_edit_multiline_mr_comment' | ||||||
|  |       MR_REMOVE_MULTILINE_COMMENT_ACTION = 'i_code_review_user_remove_multiline_mr_comment' | ||||||
| 
 | 
 | ||||||
|       class << self |       class << self | ||||||
|         def track_mr_diffs_action(merge_request:) |         def track_mr_diffs_action(merge_request:) | ||||||
|  | @ -42,16 +45,19 @@ module Gitlab | ||||||
|           track_unique_action_by_user(MR_REOPEN_ACTION, user) |           track_unique_action_by_user(MR_REOPEN_ACTION, user) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def track_create_comment_action(user:) |         def track_create_comment_action(note:) | ||||||
|           track_unique_action_by_user(MR_CREATE_COMMENT_ACTION, user) |           track_unique_action_by_user(MR_CREATE_COMMENT_ACTION, note.author) | ||||||
|  |           track_multiline_unique_action(MR_CREATE_MULTILINE_COMMENT_ACTION, note) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def track_edit_comment_action(user:) |         def track_edit_comment_action(note:) | ||||||
|           track_unique_action_by_user(MR_EDIT_COMMENT_ACTION, user) |           track_unique_action_by_user(MR_EDIT_COMMENT_ACTION, note.author) | ||||||
|  |           track_multiline_unique_action(MR_EDIT_MULTILINE_COMMENT_ACTION, note) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def track_remove_comment_action(user:) |         def track_remove_comment_action(note:) | ||||||
|           track_unique_action_by_user(MR_REMOVE_COMMENT_ACTION, user) |           track_unique_action_by_user(MR_REMOVE_COMMENT_ACTION, note.author) | ||||||
|  |           track_multiline_unique_action(MR_REMOVE_MULTILINE_COMMENT_ACTION, note) | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def track_create_review_note_action(user:) |         def track_create_review_note_action(user:) | ||||||
|  | @ -77,6 +83,12 @@ module Gitlab | ||||||
|         def track_unique_action(action, value) |         def track_unique_action(action, value) | ||||||
|           Gitlab::UsageDataCounters::HLLRedisCounter.track_usage_event(action, value) |           Gitlab::UsageDataCounters::HLLRedisCounter.track_usage_event(action, value) | ||||||
|         end |         end | ||||||
|  | 
 | ||||||
|  |         def track_multiline_unique_action(action, note) | ||||||
|  |           return unless note.is_a?(DiffNote) && note.multiline? | ||||||
|  | 
 | ||||||
|  |           track_unique_action_by_user(action, note.author) | ||||||
|  |         end | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  |  | ||||||
|  | @ -15042,6 +15042,9 @@ msgstr "" | ||||||
| msgid "Incidents|Must start with http or https" | msgid "Incidents|Must start with http or https" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Incidents|There was an issue deleting the image." | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Incidents|There was an issue loading metric images." | msgid "Incidents|There was an issue loading metric images." | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | @ -15054,6 +15057,12 @@ msgstr "" | ||||||
| msgid "Incident|Alert details" | msgid "Incident|Alert details" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Incident|Are you sure you wish to delete this image?" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
|  | msgid "Incident|Deleting %{filename}" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Incident|Metrics" | msgid "Incident|Metrics" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -1,53 +0,0 @@ | ||||||
| import { shallowMount } from '@vue/test-utils'; |  | ||||||
| import Component from '~/projects/pipelines/charts/components/app_legacy.vue'; |  | ||||||
| import PipelineCharts from '~/projects/pipelines/charts/components/pipeline_charts.vue'; |  | ||||||
| import { |  | ||||||
|   counts, |  | ||||||
|   timesChartData, |  | ||||||
|   areaChartData as lastWeekChartData, |  | ||||||
|   areaChartData as lastMonthChartData, |  | ||||||
|   lastYearChartData, |  | ||||||
| } from '../mock_data'; |  | ||||||
| 
 |  | ||||||
| describe('ProjectsPipelinesChartsApp', () => { |  | ||||||
|   let wrapper; |  | ||||||
| 
 |  | ||||||
|   beforeEach(() => { |  | ||||||
|     wrapper = shallowMount(Component, { |  | ||||||
|       propsData: { |  | ||||||
|         counts, |  | ||||||
|         timesChartData, |  | ||||||
|         lastWeekChartData, |  | ||||||
|         lastMonthChartData, |  | ||||||
|         lastYearChartData, |  | ||||||
|       }, |  | ||||||
|       provide: { |  | ||||||
|         projectPath: 'test/project', |  | ||||||
|         shouldRenderDeploymentFrequencyCharts: true, |  | ||||||
|       }, |  | ||||||
|       stubs: { |  | ||||||
|         DeploymentFrequencyCharts: true, |  | ||||||
|       }, |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   afterEach(() => { |  | ||||||
|     wrapper.destroy(); |  | ||||||
|     wrapper = null; |  | ||||||
|   }); |  | ||||||
| 
 |  | ||||||
|   describe('pipelines charts', () => { |  | ||||||
|     it('displays the pipeline charts', () => { |  | ||||||
|       const chart = wrapper.find(PipelineCharts); |  | ||||||
| 
 |  | ||||||
|       expect(chart.exists()).toBe(true); |  | ||||||
|       expect(chart.props()).toMatchObject({ |  | ||||||
|         counts, |  | ||||||
|         lastWeek: lastWeekChartData, |  | ||||||
|         lastMonth: lastMonthChartData, |  | ||||||
|         lastYear: lastYearChartData, |  | ||||||
|         timesChart: timesChartData, |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|   }); |  | ||||||
| }); |  | ||||||
|  | @ -5,7 +5,7 @@ require 'spec_helper' | ||||||
| RSpec.describe Gitlab::Ci::Config::Entry::Variables do | RSpec.describe Gitlab::Ci::Config::Entry::Variables do | ||||||
|   let(:metadata) { {} } |   let(:metadata) { {} } | ||||||
| 
 | 
 | ||||||
|   subject { described_class.new(config, metadata) } |   subject { described_class.new(config, **metadata) } | ||||||
| 
 | 
 | ||||||
|   shared_examples 'valid config' do |   shared_examples 'valid config' do | ||||||
|     describe '#value' do |     describe '#value' do | ||||||
|  |  | ||||||
|  | @ -752,4 +752,62 @@ RSpec.describe Gitlab::Diff::Position do | ||||||
|       expect(subject.file_hash).to eq(Digest::SHA1.hexdigest(subject.file_path)) |       expect(subject.file_hash).to eq(Digest::SHA1.hexdigest(subject.file_path)) | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
|  | 
 | ||||||
|  |   describe '#multiline?' do | ||||||
|  |     let(:end_line_code) { "ab09011fa121d0a2bb9fa4ca76094f2482b902b7_#{end_old_line}_#{end_new_line}" } | ||||||
|  | 
 | ||||||
|  |     let(:line_range) do | ||||||
|  |       { | ||||||
|  |         "start" => { | ||||||
|  |           "line_code" => "ab09011fa121d0a2bb9fa4ca76094f2482b902b7_18_18", | ||||||
|  |           "type" => nil, | ||||||
|  |           "old_line" => 18, | ||||||
|  |           "new_line" => 18 | ||||||
|  |         }, | ||||||
|  |          "end" => { | ||||||
|  |           "line_code" => end_line_code, | ||||||
|  |           "type" => nil, | ||||||
|  |           "old_line" => end_old_line, | ||||||
|  |           "new_line" => end_new_line | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     subject(:multiline) do | ||||||
|  |       described_class.new( | ||||||
|  |         line_range: line_range, | ||||||
|  |         position_type: position_type | ||||||
|  |       ) | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     let(:end_old_line) { 20 } | ||||||
|  |     let(:end_new_line) { 20 } | ||||||
|  | 
 | ||||||
|  |     context 'when the position type is text' do | ||||||
|  |       let(:position_type) { "text" } | ||||||
|  | 
 | ||||||
|  |       context 'when the start lines equal the end lines' do | ||||||
|  |         let(:end_old_line) { 18 } | ||||||
|  |         let(:end_new_line) { 18 } | ||||||
|  | 
 | ||||||
|  |         it "returns true" do | ||||||
|  |           expect(subject.multiline?).to be_falsey | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       context 'when the start lines do not equal the end lines' do | ||||||
|  |         it "returns true" do | ||||||
|  |           expect(subject.multiline?).to be_truthy | ||||||
|  |         end | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the position type is not text' do | ||||||
|  |       let(:position_type) { "image" } | ||||||
|  | 
 | ||||||
|  |       it "returns false" do | ||||||
|  |         expect(subject.multiline?).to be_falsey | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  |   end | ||||||
| end | end | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ require 'spec_helper' | ||||||
| RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :clean_gitlab_redis_shared_state do | RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :clean_gitlab_redis_shared_state do | ||||||
|   let(:merge_request) { build(:merge_request, id: 1) } |   let(:merge_request) { build(:merge_request, id: 1) } | ||||||
|   let(:user) { build(:user, id: 1) } |   let(:user) { build(:user, id: 1) } | ||||||
|  |   let(:note) { build(:note, author: user) } | ||||||
| 
 | 
 | ||||||
|   shared_examples_for 'a tracked merge request unique event' do |   shared_examples_for 'a tracked merge request unique event' do | ||||||
|     specify do |     specify do | ||||||
|  | @ -73,27 +74,63 @@ RSpec.describe Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter, :cl | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '.track_create_comment_action' do |   describe '.track_create_comment_action' do | ||||||
|     subject { described_class.track_create_comment_action(user: user) } |     subject { described_class.track_create_comment_action(note: note) } | ||||||
| 
 | 
 | ||||||
|     it_behaves_like 'a tracked merge request unique event' do |     it_behaves_like 'a tracked merge request unique event' do | ||||||
|       let(:action) { described_class::MR_CREATE_COMMENT_ACTION } |       let(:action) { described_class::MR_CREATE_COMMENT_ACTION } | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the note is multiline diff note' do | ||||||
|  |       let(:note) { build(:diff_note_on_merge_request, author: user) } | ||||||
|  | 
 | ||||||
|  |       before do | ||||||
|  |         allow(note).to receive(:multiline?).and_return(true) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it_behaves_like 'a tracked merge request unique event' do | ||||||
|  |         let(:action) { described_class::MR_CREATE_MULTILINE_COMMENT_ACTION } | ||||||
|  |       end | ||||||
|  |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '.track_edit_comment_action' do |   describe '.track_edit_comment_action' do | ||||||
|     subject { described_class.track_edit_comment_action(user: user) } |     subject { described_class.track_edit_comment_action(note: note) } | ||||||
| 
 | 
 | ||||||
|     it_behaves_like 'a tracked merge request unique event' do |     it_behaves_like 'a tracked merge request unique event' do | ||||||
|       let(:action) { described_class::MR_EDIT_COMMENT_ACTION } |       let(:action) { described_class::MR_EDIT_COMMENT_ACTION } | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the note is multiline diff note' do | ||||||
|  |       let(:note) { build(:diff_note_on_merge_request, author: user) } | ||||||
|  | 
 | ||||||
|  |       before do | ||||||
|  |         allow(note).to receive(:multiline?).and_return(true) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it_behaves_like 'a tracked merge request unique event' do | ||||||
|  |         let(:action) { described_class::MR_EDIT_MULTILINE_COMMENT_ACTION } | ||||||
|  |       end | ||||||
|  |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '.track_remove_comment_action' do |   describe '.track_remove_comment_action' do | ||||||
|     subject { described_class.track_remove_comment_action(user: user) } |     subject { described_class.track_remove_comment_action(note: note) } | ||||||
| 
 | 
 | ||||||
|     it_behaves_like 'a tracked merge request unique event' do |     it_behaves_like 'a tracked merge request unique event' do | ||||||
|       let(:action) { described_class::MR_REMOVE_COMMENT_ACTION } |       let(:action) { described_class::MR_REMOVE_COMMENT_ACTION } | ||||||
|     end |     end | ||||||
|  | 
 | ||||||
|  |     context 'when the note is multiline diff note' do | ||||||
|  |       let(:note) { build(:diff_note_on_merge_request, author: user) } | ||||||
|  | 
 | ||||||
|  |       before do | ||||||
|  |         allow(note).to receive(:multiline?).and_return(true) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it_behaves_like 'a tracked merge request unique event' do | ||||||
|  |         let(:action) { described_class::MR_REMOVE_MULTILINE_COMMENT_ACTION } | ||||||
|  |       end | ||||||
|  |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   describe '.track_create_review_note_action' do |   describe '.track_create_review_note_action' do | ||||||
|  |  | ||||||
|  | @ -120,7 +120,7 @@ RSpec.describe Notes::CreateService do | ||||||
|           end |           end | ||||||
| 
 | 
 | ||||||
|           it 'tracks merge request usage data' do |           it 'tracks merge request usage data' do | ||||||
|             expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).to receive(:track_create_comment_action).with(user: user) |             expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).to receive(:track_create_comment_action).with(note: kind_of(Note)) | ||||||
| 
 | 
 | ||||||
|             described_class.new(project_with_repo, user, new_opts).execute |             described_class.new(project_with_repo, user, new_opts).execute | ||||||
|           end |           end | ||||||
|  |  | ||||||
|  | @ -38,7 +38,7 @@ RSpec.describe Notes::DestroyService do | ||||||
|     it 'tracks merge request usage data' do |     it 'tracks merge request usage data' do | ||||||
|       mr = create(:merge_request, source_project: project) |       mr = create(:merge_request, source_project: project) | ||||||
|       note = create(:note, project: project, noteable: mr) |       note = create(:note, project: project, noteable: mr) | ||||||
|       expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).to receive(:track_remove_comment_action).with(user: user) |       expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).to receive(:track_remove_comment_action).with(note: note) | ||||||
| 
 | 
 | ||||||
|       described_class.new(project, user).execute(note) |       described_class.new(project, user).execute(note) | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -69,7 +69,7 @@ RSpec.describe Notes::UpdateService do | ||||||
|       let(:note) { create(:note, project: project, noteable: merge_request, author: user, note: "Old note #{user2.to_reference}") } |       let(:note) { create(:note, project: project, noteable: merge_request, author: user, note: "Old note #{user2.to_reference}") } | ||||||
| 
 | 
 | ||||||
|       it 'tracks merge request usage data' do |       it 'tracks merge request usage data' do | ||||||
|         expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).to receive(:track_edit_comment_action).with(user: user) |         expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter).to receive(:track_edit_comment_action).with(note: note) | ||||||
| 
 | 
 | ||||||
|         update_note(note: 'new text') |         update_note(note: 'new text') | ||||||
|       end |       end | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue