Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue1
-rw-r--r--app/assets/javascripts/commit/pipelines/pipelines_table.vue9
-rw-r--r--app/assets/javascripts/graphql_shared/fragments/epic.fragment.graphql1
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue1
-rw-r--r--app/assets/javascripts/packages/details/store/getters.js4
-rw-r--r--app/assets/javascripts/pages/admin/projects/index/index.js4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue4
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue14
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue8
-rw-r--r--app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue8
-rw-r--r--app/helpers/boards_helper.rb8
-rw-r--r--app/views/admin/application_settings/_performance_bar.html.haml4
-rw-r--r--app/views/projects/_new_project_fields.html.haml2
-rw-r--r--app/views/projects/milestones/_form.html.haml8
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/mirrors/_mirror_repos.html.haml2
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml2
-rw-r--r--app/views/shared/projects/_list.html.haml6
-rw-r--r--changelogs/unreleased/263532-instrument-usage-ping-count-number-of-enabled-integrations-per-pro.yml5
-rw-r--r--changelogs/unreleased/btn-confirm-project-milestones.yml5
-rw-r--r--changelogs/unreleased/btn-confirm-project-mirrors.yml5
-rw-r--r--changelogs/unreleased/cromefire_-master-patch-00495.yml5
-rw-r--r--changelogs/unreleased/hide-mr-counts-in-project-list-where-mrs-disabled.yml5
-rw-r--r--changelogs/unreleased/pb-add-hourglass-icon-to-empty-duration-cell.yml5
-rw-r--r--config/feature_flags/development/value_stream_analytics_extended_form.yml2
-rw-r--r--config/metrics/counts_all/20210309165717_projects_with_enabled_alert_integrations_histogram.yml20
-rw-r--r--config/metrics/schema.json2
-rw-r--r--doc/ci/review_apps/index.md4
-rw-r--r--doc/development/cicd/templates.md4
-rw-r--r--doc/development/usage_ping/dictionary.md24
-rw-r--r--doc/development/usage_ping/metrics_dictionary.md2
-rw-r--r--doc/development/value_stream_analytics.md6
-rw-r--r--doc/user/group/value_stream_analytics/img/delete_value_stream_v13_4.png (renamed from doc/user/group/value_stream_analytics/img/delete_value_stream_v13.4.png)bin29439 -> 29439 bytes
-rw-r--r--doc/user/group/value_stream_analytics/img/extended_value_stream_form_v13_10.pngbin0 -> 259265 bytes
-rw-r--r--doc/user/group/value_stream_analytics/img/vsa_custom_stage_v13_10.pngbin0 -> 47083 bytes
-rw-r--r--doc/user/group/value_stream_analytics/img/vsa_default_stage_v13_10.pngbin0 -> 43974 bytes
-rw-r--r--doc/user/group/value_stream_analytics/img/vsa_filter_bar_v13_3.png (renamed from doc/user/group/value_stream_analytics/img/vsa_filter_bar_v13.3.png)bin33694 -> 33694 bytes
-rw-r--r--doc/user/group/value_stream_analytics/index.md53
-rw-r--r--doc/user/packages/dependency_proxy/index.md13
-rw-r--r--lib/api/groups.rb6
-rw-r--r--lib/gitlab/usage_data.rb8
-rw-r--r--lib/gitlab/utils/usage_data.rb69
-rw-r--r--locale/gitlab.pot12
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/admin/settings/component/ip_limits.rb6
-rw-r--r--qa/qa/page/admin/settings/component/outbound_requests.rb2
-rw-r--r--qa/qa/page/admin/settings/component/performance_bar.rb2
-rw-r--r--qa/qa/page/admin/settings/component/sign_up_restrictions.rb8
-rw-r--r--qa/qa/page/base.rb23
-rw-r--r--qa/qa/page/group/settings/general.rb48
-rw-r--r--qa/qa/page/merge_request/show.rb1
-rw-r--r--qa/qa/page/project/new.rb2
-rw-r--r--qa/qa/page/project/operations/kubernetes/add_existing.rb2
-rw-r--r--qa/qa/page/project/settings/auto_devops.rb4
-rw-r--r--qa/qa/page/project/settings/ci_variables.rb1
-rw-r--r--qa/qa/page/project/settings/incidents.rb2
-rw-r--r--qa/qa/page/project/settings/merge_request.rb2
-rw-r--r--qa/qa/page/project/web_ide/edit.rb4
-rw-r--r--rubocop/rubocop-usage-data.yml1
-rwxr-xr-xscripts/lint-doc.sh6
-rw-r--r--spec/frontend/packages/details/store/getters_spec.js4
-rw-r--r--spec/frontend/pipelines/graph/linked_pipelines_column_spec.js4
-rw-r--r--spec/frontend/pipelines/time_ago_spec.js18
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js51
-rw-r--r--spec/helpers/boards_helper_spec.rb10
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb17
-rw-r--r--spec/lib/gitlab/utils/usage_data_spec.rb98
-rw-r--r--spec/support/helpers/database/database_helpers.rb56
-rw-r--r--yarn.lock8
70 files changed, 587 insertions, 140 deletions
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
index af4349083b6..be7c0b68b4c 100644
--- a/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
+++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue
@@ -265,7 +265,6 @@ export default {
<gl-form-checkbox
ref="masked-ci-variable"
v-model="masked"
- data-qa-selector="ci_variable_masked_checkbox"
data-testid="ci-variable-masked-checkbox"
>
{{ __('Mask variable') }}
diff --git a/app/assets/javascripts/commit/pipelines/pipelines_table.vue b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
index ca4d8da2482..e1cca5adc73 100644
--- a/app/assets/javascripts/commit/pipelines/pipelines_table.vue
+++ b/app/assets/javascripts/commit/pipelines/pipelines_table.vue
@@ -8,6 +8,7 @@ import PipelinesMixin from '~/pipelines/mixins/pipelines_mixin';
import PipelinesService from '~/pipelines/services/pipelines_service';
import PipelineStore from '~/pipelines/stores/pipelines_store';
import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
@@ -19,7 +20,7 @@ export default {
TablePagination,
SvgBlankState,
},
- mixins: [PipelinesMixin],
+ mixins: [PipelinesMixin, glFeatureFlagMixin()],
props: {
endpoint: {
type: String,
@@ -90,6 +91,9 @@ export default {
canRenderPipelineButton() {
return this.latestPipelineDetachedFlag;
},
+ pipelineButtonClass() {
+ return !this.glFeatures.newPipelinesTable ? 'gl-md-display-none' : 'gl-lg-display-none';
+ },
isForkMergeRequest() {
return this.sourceProjectFullPath !== this.targetProjectFullPath;
},
@@ -192,7 +196,8 @@ export default {
<gl-button
v-if="canRenderPipelineButton"
block
- class="gl-mt-3 gl-mb-3 gl-md-display-none"
+ class="gl-mt-3 gl-mb-3"
+ :class="pipelineButtonClass"
variant="success"
data-testid="run_pipeline_button_mobile"
:loading="state.isRunningMergeRequestPipeline"
diff --git a/app/assets/javascripts/graphql_shared/fragments/epic.fragment.graphql b/app/assets/javascripts/graphql_shared/fragments/epic.fragment.graphql
index 286ebbd019e..b5b4ba4e772 100644
--- a/app/assets/javascripts/graphql_shared/fragments/epic.fragment.graphql
+++ b/app/assets/javascripts/graphql_shared/fragments/epic.fragment.graphql
@@ -4,6 +4,7 @@ fragment EpicNode on Epic {
title
state
reference
+ webPath
webUrl
createdAt
closedAt
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
index 121dae4b87e..43bf2e1a90c 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue
@@ -41,7 +41,6 @@ export default {
:disabled="shouldDisableNewMrOption"
:checked="shouldCreateMR"
type="checkbox"
- data-qa-selector="start_new_mr_checkbox"
@change="toggleShouldCreateMR"
/>
<span class="gl-ml-3 ide-option-label">
diff --git a/app/assets/javascripts/packages/details/store/getters.js b/app/assets/javascripts/packages/details/store/getters.js
index fd75d476b86..fb9b7d61fd2 100644
--- a/app/assets/javascripts/packages/details/store/getters.js
+++ b/app/assets/javascripts/packages/details/store/getters.js
@@ -122,8 +122,8 @@ export const gradleGroovyInstalCommand = ({ packageEntity }) => {
export const gradleGroovyAddSourceCommand = ({ mavenPath }) =>
// eslint-disable-next-line @gitlab/require-i18n-strings
- `gitlab {
- url "${mavenPath}"
+ `maven {
+ url '${mavenPath}'
}`;
export const groupExists = ({ groupListUrl }) => groupListUrl.length > 0;
diff --git a/app/assets/javascripts/pages/admin/projects/index/index.js b/app/assets/javascripts/pages/admin/projects/index/index.js
index 1971323abac..cc9a9b6cc38 100644
--- a/app/assets/javascripts/pages/admin/projects/index/index.js
+++ b/app/assets/javascripts/pages/admin/projects/index/index.js
@@ -6,7 +6,7 @@ import Translate from '~/vue_shared/translate';
import deleteProjectModal from './components/delete_project_modal.vue';
-document.addEventListener('DOMContentLoaded', () => {
+(() => {
Vue.use(Translate);
const deleteProjectModalEl = document.getElementById('delete-project-modal');
@@ -39,4 +39,4 @@ document.addEventListener('DOMContentLoaded', () => {
});
},
});
-});
+})();
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index fc0a8b07e7f..ec50253c939 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -106,8 +106,8 @@ export default {
height: this.$refs[this.containerId].scrollHeight,
};
},
- onError(errorType) {
- this.$emit('error', errorType);
+ onError(payload) {
+ this.$emit('error', payload);
},
setJob(jobName) {
this.hoveredJobName = jobName;
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
index ed33a94af6e..962f2ca2a4c 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
@@ -73,7 +73,11 @@ export default {
return unwrapPipelineData(this.pipelineProjectPath, data);
},
error(err) {
- this.reportFailure(LOAD_FAILURE, serializeLoadErrors(err));
+ this.reportFailure({ type: LOAD_FAILURE, skipSentry: true });
+ reportToSentry(
+ this.$options.name,
+ `type: ${LOAD_FAILURE}, info: ${serializeLoadErrors(err)}`,
+ );
},
result({ error }) {
/*
@@ -134,11 +138,15 @@ export default {
refreshPipelineGraph() {
this.$apollo.queries.pipeline.refetch();
},
- reportFailure(type, err = '') {
+ /* eslint-disable @gitlab/require-i18n-strings */
+ reportFailure({ type, err = 'No error string passed.', skipSentry = false }) {
this.showAlert = true;
this.alertType = type;
- reportToSentry(this.$options.name, `type: ${this.alertType}, info: ${err}`);
+ if (!skipSentry) {
+ reportToSentry(this.$options.name, `type: ${type}, info: ${err}`);
+ }
},
+ /* eslint-enable @gitlab/require-i18n-strings */
},
};
</script>
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
index 939400eb1c3..b55a77a3c4f 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -111,14 +111,12 @@ export default {
this.loadingPipelineId = null;
this.$emit('scrollContainer');
},
- error(err, _vm, _key, type) {
- this.$emit('error', LOAD_FAILURE);
+ error(err) {
+ this.$emit('error', { type: LOAD_FAILURE, skipSentry: true });
reportToSentry(
'linked_pipelines_column',
- `error type: ${LOAD_FAILURE}, error: ${serializeLoadErrors(
- err,
- )}, apollo error type: ${type}`,
+ `error type: ${LOAD_FAILURE}, error: ${serializeLoadErrors(err)}`,
);
},
});
diff --git a/app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue b/app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue
index 84ca0bf1443..f4215d67292 100644
--- a/app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue
+++ b/app/assets/javascripts/pipelines/components/graph_shared/links_inner.vue
@@ -170,7 +170,7 @@ export default {
const parsedData = parseData(arrayOfJobs);
this.links = generateLinksData(parsedData, this.containerId, `-${this.pipelineId}`);
} catch (err) {
- this.$emit('error', DRAW_FAILURE);
+ this.$emit('error', { type: DRAW_FAILURE, reportToSentry: false });
reportToSentry(this.$options.name, err);
}
this.finishPerfMeasureAndSend();
diff --git a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
index 278089b6155..543bdf94307 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_list/time_ago.vue
@@ -48,6 +48,9 @@ export default {
legacyTableMobileClass() {
return !this.glFeatures.newPipelinesTable ? 'table-mobile-content' : '';
},
+ showInProgress() {
+ return !this.duration && !this.finishedTime;
+ },
},
};
</script>
@@ -57,6 +60,11 @@ export default {
{{ s__('Pipeline|Duration') }}
</div>
<div :class="legacyTableMobileClass">
+ <span v-if="showInProgress" data-testid="pipeline-in-progress">
+ <gl-icon name="hourglass" class="gl-vertical-align-baseline! gl-mr-2" :size="12" />
+ {{ s__('Pipeline|In progress') }}
+ </span>
+
<p v-if="duration" class="duration">
<gl-icon name="timer" class="gl-vertical-align-baseline!" :size="12" />
{{ durationFormatted }}
diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb
index 73e7476b55d..b8c2255ab7e 100644
--- a/app/helpers/boards_helper.rb
+++ b/app/helpers/boards_helper.rb
@@ -18,7 +18,7 @@ module BoardsHelper
time_tracking_limit_to_hours: Gitlab::CurrentSettings.time_tracking_limit_to_hours.to_s,
recent_boards_endpoint: recent_boards_path,
parent: current_board_parent.model_name.param_key,
- group_id: @group&.id,
+ group_id: group_id,
labels_filter_base_path: build_issue_link_base,
labels_fetch_path: labels_fetch_path,
labels_manage_path: labels_manage_path,
@@ -26,6 +26,12 @@ module BoardsHelper
}
end
+ def group_id
+ return @group.id if board.group_board?
+
+ @project&.group&.id
+ end
+
def full_path
if board.group_board?
@group.full_path
diff --git a/app/views/admin/application_settings/_performance_bar.html.haml b/app/views/admin/application_settings/_performance_bar.html.haml
index 21345e4d80e..8f2bdd109cb 100644
--- a/app/views/admin/application_settings/_performance_bar.html.haml
+++ b/app/views/admin/application_settings/_performance_bar.html.haml
@@ -4,8 +4,8 @@
%fieldset
.form-group
.form-check
- = f.check_box :performance_bar_enabled, class: 'form-check-input'
- = f.label :performance_bar_enabled, class: 'form-check-label qa-enable-performance-bar-checkbox' do
+ = f.check_box :performance_bar_enabled, class: 'form-check-input', data: { qa_selector: 'enable_performance_bar_checkbox'}
+ = f.label :performance_bar_enabled, class: 'form-check-label' do
Enable access to the Performance Bar
.form-group
= f.label :performance_bar_allowed_group_path, 'Allowed group', class: 'label-bold'
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index f6b635173a5..8b1bf37ff10 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -55,7 +55,7 @@
%div{ :class => "col-sm-12" }
.form-check
- experiment(:new_project_readme, actor: current_user) do |e|
- = check_box_tag 'project[initialize_with_readme]', '1', e.run, class: 'form-check-input qa-initialize-with-readme-checkbox', data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme", track_value: "" }
+ = check_box_tag 'project[initialize_with_readme]', '1', e.run, class: 'form-check-input', data: { qa_selector: "initialize_with_readme_checkbox", track_label: "#{track_label}", track_event: "activate_form_input", track_property: "init_with_readme", track_value: "" }
= label_tag 'project[initialize_with_readme]', class: 'form-check-label' do
.option-title
%strong= s_('ProjectsNew|Initialize repository with a README')
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index f6e4442d4fb..56906eb6e66 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -21,8 +21,8 @@
.form-actions
- if @milestone.new_record?
- = f.submit _('Create milestone'), class: 'gl-button btn-success btn', data: { qa_selector: 'create_milestone_button' }
- = link_to _('Cancel'), project_milestones_path(@project), class: 'gl-button btn btn-cancel'
+ = f.submit _('Create milestone'), class: 'gl-button btn-confirm btn', data: { qa_selector: 'create_milestone_button' }
+ = link_to _('Cancel'), project_milestones_path(@project), class: 'gl-button btn btn-default btn-cancel'
- else
- = f.submit _('Save changes'), class: 'gl-button btn-success btn'
- = link_to _('Cancel'), project_milestone_path(@project, @milestone), class: 'gl-button btn btn-cancel'
+ = f.submit _('Save changes'), class: 'gl-button btn-confirm btn'
+ = link_to _('Cancel'), project_milestone_path(@project, @milestone), class: 'gl-button btn btn-default btn-cancel'
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index c0df986e1ca..059ef53c42b 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -8,7 +8,7 @@
= render 'shared/milestones/search_form'
= render 'shared/milestones_sort_dropdown'
- if can?(current_user, :admin_milestone, @project)
- = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-success', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
+ = link_to new_project_milestone_path(@project), class: 'gl-button btn btn-confirm', data: { qa_selector: "new_project_milestone_link" }, title: _('New milestone') do
= _('New milestone')
- if @milestones.blank?
diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml
index bcdc2988e49..5a1e263141d 100644
--- a/app/views/projects/mirrors/_mirror_repos.html.haml
+++ b/app/views/projects/mirrors/_mirror_repos.html.haml
@@ -35,7 +35,7 @@
= link_to _('Learn more.'), help_page_path('user/project/repository/repository_mirroring', anchor: 'mirror-only-protected-branches'), target: '_blank', rel: 'noopener noreferrer'
.panel-footer
- = f.submit _('Mirror repository'), class: 'gl-button btn btn-success js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror
+ = f.submit _('Mirror repository'), class: 'gl-button btn btn-confirm js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror
- else
.gl-alert.gl-alert-info{ role: 'alert' }
= sprite_icon('information-o', css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 904b3d6f483..58d125acc2d 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -56,7 +56,7 @@
%tr.build-state.responsive-table-border-start
%td.responsive-table-cell.ci-status-icon-failed{ data: { column: _('Status')} }
.d-none.d-md-block.build-icon
- = custom_icon("icon_status_#{build.status}")
+ = sprite_icon("status_#{build.status}")
.d-md-none.build-badge
= render "ci/status/badge", link: false, status: job.detailed_status(current_user)
%td.responsive-table-cell.build-name{ data: { column: _('Name')} }
diff --git a/app/views/shared/projects/_list.html.haml b/app/views/shared/projects/_list.html.haml
index 32b1af50a01..0210ed8c9b3 100644
--- a/app/views/shared/projects/_list.html.haml
+++ b/app/views/shared/projects/_list.html.haml
@@ -3,7 +3,6 @@
- use_creator_avatar = false unless local_assigns[:use_creator_avatar] == true
- stars = true unless local_assigns[:stars] == false
- forks = true unless local_assigns[:forks] == false
-- merge_requests = true unless local_assigns[:merge_requests] == false
- pipeline_status = true unless local_assigns[:pipeline_status] == false
- skip_namespace = false unless local_assigns[:skip_namespace] == true
- user = local_assigns[:user]
@@ -39,8 +38,9 @@
- css_class = (i >= projects_limit) || project.pending_delete? ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, use_creator_avatar: use_creator_avatar,
- forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user, merge_requests: merge_requests,
- issues: project.issues_enabled?, pipeline_status: pipeline_status, compact_mode: compact_mode
+ forks: forks, show_last_commit_as_description: show_last_commit_as_description, user: user,
+ merge_requests: project.merge_requests_enabled?, issues: project.issues_enabled?,
+ pipeline_status: pipeline_status, compact_mode: compact_mode
= paginate_collection(projects, remote: remote) unless skip_pagination
- else
- if @contributed_projects
diff --git a/changelogs/unreleased/263532-instrument-usage-ping-count-number-of-enabled-integrations-per-pro.yml b/changelogs/unreleased/263532-instrument-usage-ping-count-number-of-enabled-integrations-per-pro.yml
new file mode 100644
index 00000000000..d08f1a76fe2
--- /dev/null
+++ b/changelogs/unreleased/263532-instrument-usage-ping-count-number-of-enabled-integrations-per-pro.yml
@@ -0,0 +1,5 @@
+---
+title: 'Usage ping: Histogram for enabled integrations per project'
+merge_request: 55782
+author:
+type: added
diff --git a/changelogs/unreleased/btn-confirm-project-milestones.yml b/changelogs/unreleased/btn-confirm-project-milestones.yml
new file mode 100644
index 00000000000..b03e041f1a4
--- /dev/null
+++ b/changelogs/unreleased/btn-confirm-project-milestones.yml
@@ -0,0 +1,5 @@
+---
+title: Move from btn-success to btn-confirm in milestones directory
+merge_request: 56342
+author: Yogi (@yo)
+type: changed
diff --git a/changelogs/unreleased/btn-confirm-project-mirrors.yml b/changelogs/unreleased/btn-confirm-project-mirrors.yml
new file mode 100644
index 00000000000..9f847c6fdac
--- /dev/null
+++ b/changelogs/unreleased/btn-confirm-project-mirrors.yml
@@ -0,0 +1,5 @@
+---
+title: Move from btn-success to btn-confirm in mirrors directory
+merge_request: 56343
+author: Yogi (@yo)
+type: changed
diff --git a/changelogs/unreleased/cromefire_-master-patch-00495.yml b/changelogs/unreleased/cromefire_-master-patch-00495.yml
new file mode 100644
index 00000000000..e9a9f52ae69
--- /dev/null
+++ b/changelogs/unreleased/cromefire_-master-patch-00495.yml
@@ -0,0 +1,5 @@
+---
+title: Correct generated maven repository instruction for Gradle Groovy DSL
+merge_request: 56318
+author: Cromefire_ (@cromefire_)
+type: fixed
diff --git a/changelogs/unreleased/hide-mr-counts-in-project-list-where-mrs-disabled.yml b/changelogs/unreleased/hide-mr-counts-in-project-list-where-mrs-disabled.yml
new file mode 100644
index 00000000000..be9cfbdfecd
--- /dev/null
+++ b/changelogs/unreleased/hide-mr-counts-in-project-list-where-mrs-disabled.yml
@@ -0,0 +1,5 @@
+---
+title: Hide MR count and link in project list where MRs are disabled
+merge_request: !56432
+author: Simon Stieger @sim0
+type: fixed
diff --git a/changelogs/unreleased/pb-add-hourglass-icon-to-empty-duration-cell.yml b/changelogs/unreleased/pb-add-hourglass-icon-to-empty-duration-cell.yml
new file mode 100644
index 00000000000..f7fe185d7e3
--- /dev/null
+++ b/changelogs/unreleased/pb-add-hourglass-icon-to-empty-duration-cell.yml
@@ -0,0 +1,5 @@
+---
+title: Display in progress for pipeline duration cell when pipeline has not finished running
+merge_request: 56266
+author:
+type: changed
diff --git a/config/feature_flags/development/value_stream_analytics_extended_form.yml b/config/feature_flags/development/value_stream_analytics_extended_form.yml
index 1cdb9111af4..f74c85309c1 100644
--- a/config/feature_flags/development/value_stream_analytics_extended_form.yml
+++ b/config/feature_flags/development/value_stream_analytics_extended_form.yml
@@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/294190
milestone: '13.7'
type: development
group: group::optimize
-default_enabled: false
+default_enabled: true
diff --git a/config/metrics/counts_all/20210309165717_projects_with_enabled_alert_integrations_histogram.yml b/config/metrics/counts_all/20210309165717_projects_with_enabled_alert_integrations_histogram.yml
new file mode 100644
index 00000000000..6d393fad3a3
--- /dev/null
+++ b/config/metrics/counts_all/20210309165717_projects_with_enabled_alert_integrations_histogram.yml
@@ -0,0 +1,20 @@
+---
+key_path: usage_activity_by_stage.monitor.projects_with_enabled_alert_integrations_histogram
+description: Histogram (buckets 1 to 100) of projects with at least 1 enabled integration.
+product_section: ops
+product_stage: monitor
+product_group: group::monitor
+product_category: incident_management
+value_type: object
+status: data_available
+milestone: "13.10"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55782
+time_frame: all
+data_source: database
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/schema.json b/config/metrics/schema.json
index 73a7ee65dec..cc1eafcf0ba 100644
--- a/config/metrics/schema.json
+++ b/config/metrics/schema.json
@@ -22,7 +22,7 @@
},
"value_type": {
"type": "string",
- "enum": ["string", "number", "boolean"]
+ "enum": ["string", "number", "boolean", "object"]
},
"status": {
"type": ["string"],
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index bd9a60e952c..122f9caebe7 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -286,8 +286,8 @@ The visual review tools retrieve the merge request ID from the `data-merge-reque
data attribute included in the `script` HTML tag used to add the visual review tools
to your review app.
-​After determining the ID for the merge request to link to a visual review app, you
-can supply the ID by either:​​
+After determining the ID for the merge request to link to a visual review app, you
+can supply the ID by either:
- Hard-coding it in the script tag via the data attribute `data-merge-request-id` of the app.
- Dynamically adding the `data-merge-request-id` value during the build of the app.
diff --git a/doc/development/cicd/templates.md b/doc/development/cicd/templates.md
index 3e7ae39cb7d..ed0d217c247 100644
--- a/doc/development/cicd/templates.md
+++ b/doc/development/cicd/templates.md
@@ -72,6 +72,10 @@ Please read [versioning](#versioning) section for introducing breaking change sa
When a root `.gitlab-ci.yml` [includes](../../ci/yaml/README.md#include)
multiple templates, these global keywords could be overridden by the
others and cause an unexpected behavior.
+- Include [a changelog](../changelog.md) if your merge request introduces a user-facing change.
+- Use [`$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH`](../../ci/variables/predefined_variables.md)
+ instead of a hardcoded `main` branch, and never use `master`.
+- Use [`rules`](../../ci/yaml/README.md#rules) instead of [`only` or `except`](../../ci/yaml/README.md#onlyexcept-basic), if possible.
## Versioning
diff --git a/doc/development/usage_ping/dictionary.md b/doc/development/usage_ping/dictionary.md
index 32790c44249..8860f3d1abf 100644
--- a/doc/development/usage_ping/dictionary.md
+++ b/doc/development/usage_ping/dictionary.md
@@ -1028,6 +1028,18 @@ Status: `data_available`
Tiers: `premium`, `ultimate`
+### `counts.geo_node_usage.git_fetch_event_count_weekly`
+
+Number of Git fetch events from Prometheus on the Geo secondary
+
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/config/metrics/counts_7d/20210309194425_git_fetch_event_count_weekly.yml)
+
+Group: `group::geo`
+
+Status: `implemented`
+
+Tiers: `premium`, `ultimate`
+
### `counts.geo_nodes`
Total number of sites in a Geo deployment
@@ -14840,6 +14852,18 @@ Status: `data_available`
Tiers: `free`
+### `usage_activity_by_stage.monitor.projects_with_enabled_alert_integrations_histogram`
+
+Histogram (buckets 1 to 100) of projects with at least 1 enabled integration.
+
+[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210309165717_projects_with_enabled_alert_integrations_histogram.yml)
+
+Group: `group::monitor`
+
+Status: `data_available`
+
+Tiers: `free`, `premium`, `ultimate`
+
### `usage_activity_by_stage.monitor.projects_with_error_tracking_enabled`
Projects where error tracking is enabled
diff --git a/doc/development/usage_ping/metrics_dictionary.md b/doc/development/usage_ping/metrics_dictionary.md
index 3f43aaa916f..3c08fb0cc87 100644
--- a/doc/development/usage_ping/metrics_dictionary.md
+++ b/doc/development/usage_ping/metrics_dictionary.md
@@ -32,7 +32,7 @@ Each metric is defined in a separate YAML file consisting of a number of fields:
| `product_stage` | no | The [stage](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) for the metric. |
| `product_group` | yes | The [group](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) that owns the metric. |
| `product_category` | no | The [product category](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/categories.yml) for the metric. |
-| `value_type` | yes | `string`; one of `string`, `number`, `boolean`. |
+| `value_type` | yes | `string`; one of `string`, `number`, `boolean`, `object`. |
| `status` | yes | `string`; status of the metric, may be set to `data_available`, `planned`, `in_progress`, `implemented`, `not_used`, `deprecated` |
| `time_frame` | yes | `string`; may be set to a value like `7d`, `28d`, `all`, `none`. |
| `data_source` | yes | `string`; may be set to a value like `database`, `redis`, `redis_hll`, `prometheus`, `ruby`. |
diff --git a/doc/development/value_stream_analytics.md b/doc/development/value_stream_analytics.md
index b3692bd1d2c..6b2442f1c32 100644
--- a/doc/development/value_stream_analytics.md
+++ b/doc/development/value_stream_analytics.md
@@ -90,7 +90,7 @@ Some start/end event pairs are not "compatible" with each other. For example:
- "Issue closed" to "Issue closed": Duration is always 0.
The `StageEvents` module describes the allowed `start_event` and `end_event` pairings (`PAIRING_RULES` constant). If a new event is added, it needs to be registered in this module.
-​To add a new event:​
+To add a new event:
1. Add an entry in `ENUM_MAPPING` with a unique number, which is used in the `Stage` model as `enum`.
1. Define which events are compatible with the event in the `PAIRING_RULES` hash.
@@ -190,9 +190,9 @@ Currently supported parents:
### Default stages
The [original implementation](https://gitlab.com/gitlab-org/gitlab/-/issues/847) of value stream analytics defined 7 stages. These stages are always available for each parent, however altering these stages is not possible.
-​
+
To make things efficient and reduce the number of records created, the default stages are expressed as in-memory objects (not persisted). When the user creates a custom stage for the first time, all the stages are persisted. This behavior is implemented in the value stream analytics service objects.
-​
+
The reason for this was that we'd like to add the abilities to hide and order stages later on.
## Data Collector
diff --git a/doc/user/group/value_stream_analytics/img/delete_value_stream_v13.4.png b/doc/user/group/value_stream_analytics/img/delete_value_stream_v13_4.png
index c97fcb76343..c97fcb76343 100644
--- a/doc/user/group/value_stream_analytics/img/delete_value_stream_v13.4.png
+++ b/doc/user/group/value_stream_analytics/img/delete_value_stream_v13_4.png
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/img/extended_value_stream_form_v13_10.png b/doc/user/group/value_stream_analytics/img/extended_value_stream_form_v13_10.png
new file mode 100644
index 00000000000..26508787177
--- /dev/null
+++ b/doc/user/group/value_stream_analytics/img/extended_value_stream_form_v13_10.png
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/img/vsa_custom_stage_v13_10.png b/doc/user/group/value_stream_analytics/img/vsa_custom_stage_v13_10.png
new file mode 100644
index 00000000000..77f4a26b880
--- /dev/null
+++ b/doc/user/group/value_stream_analytics/img/vsa_custom_stage_v13_10.png
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/img/vsa_default_stage_v13_10.png b/doc/user/group/value_stream_analytics/img/vsa_default_stage_v13_10.png
new file mode 100644
index 00000000000..1adb114b025
--- /dev/null
+++ b/doc/user/group/value_stream_analytics/img/vsa_default_stage_v13_10.png
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/img/vsa_filter_bar_v13.3.png b/doc/user/group/value_stream_analytics/img/vsa_filter_bar_v13_3.png
index 506765f63cb..506765f63cb 100644
--- a/doc/user/group/value_stream_analytics/img/vsa_filter_bar_v13.3.png
+++ b/doc/user/group/value_stream_analytics/img/vsa_filter_bar_v13_3.png
Binary files differ
diff --git a/doc/user/group/value_stream_analytics/index.md b/doc/user/group/value_stream_analytics/index.md
index 4fcef07a04e..52cf51d85a4 100644
--- a/doc/user/group/value_stream_analytics/index.md
+++ b/doc/user/group/value_stream_analytics/index.md
@@ -59,7 +59,7 @@ To filter results:
1. Select a parameter to filter by.
1. Select a value from the autocompleted results, or type to refine the results.
-![Value stream analytics filter bar](img/vsa_filter_bar_v13.3.png "Active filter bar for value stream analytics")
+![Value stream analytics filter bar](img/vsa_filter_bar_v13_3.png "Active filter bar for value stream analytics")
### Date ranges
@@ -299,10 +299,59 @@ To create a value stream:
1. Navigate to your group's **Analytics > Value Stream**.
1. Click the Value stream dropdown and select **Create new Value Stream**
1. Fill in a name for the new Value Stream
+ - You can [customize the stages](#creating-a-value-stream-with-stages) as the `value_stream_analytics_extended_form` feature flag is enabled.
1. Click the **Create Value Stream** button.
![New value stream](img/new_value_stream_v13_3.png "Creating a new value stream")
+#### Creating a value stream with stages
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/55572) in GitLab 13.10.
+> - It's [deployed behind a feature flag](../../feature_flags.md), enabled by default.
+> - It's enabled on GitLab.com.
+> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](../../../administration/feature_flags.md). **(FREE SELF)**
+
+WARNING:
+This feature might not be available to you. Check the **version history** note above for details.
+
+You can create value streams with stages, starting with a default or a blank template. You can
+add stages as desired.
+
+To create a value stream with stages:
+
+1. Navigate to your group's **Analytics > Value Stream**.
+1. Find and select the Value Stream dropdown. Select **Create new Value Stream**.
+1. Select either **Create from default template** or **Create from no template**.
+ - Default stages in the value stream can be hidden or re-ordered
+ ![Default stage actions](img/vsa_default_stage_v13_10.png "Default stage actions")
+ - New stages can be added by clicking the 'Add another stage' button
+ - The name, start and end events for the stage can be selected
+ ![Custom stage actions](img/vsa_custom_stage_v13_10.png "Custom stage actions")
+1. Select the **Create Value Stream** button to save the value stream.
+
+![Extended create value stream form](img/extended_value_stream_form_v13_10.png "Extended create value stream form")
+
+#### Enable or disable value stream with stages
+
+Value streams with stages is under development but 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(:value_stream_analytics_extended_form)
+```
+
+To disable it:
+
+```ruby
+# For the instance
+Feature.disable(:value_stream_analytics_extended_form)
+```
+
### Deleting a value stream
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221205) in GitLab 13.4.
@@ -314,7 +363,7 @@ To delete a custom value stream:
1. Click the **Delete (name of value stream)**.
1. Click the **Delete** button to confirm.
-![Delete value stream](img/delete_value_stream_v13.4.png "Deleting a custom value stream")
+![Delete value stream](img/delete_value_stream_v13_4.png "Deleting a custom value stream")
## Days to completion chart
diff --git a/doc/user/packages/dependency_proxy/index.md b/doc/user/packages/dependency_proxy/index.md
index ce0b6fcaddd..ad2d2ac2a8e 100644
--- a/doc/user/packages/dependency_proxy/index.md
+++ b/doc/user/packages/dependency_proxy/index.md
@@ -95,9 +95,11 @@ Runners log in to the Dependency Proxy automatically. To pull through
the Dependency Proxy, use the `CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX`
[predefined CI/CD variable](../../../ci/variables/predefined_variables.md):
+Example pulling the latest alpine image:
+
```yaml
# .gitlab-ci.yml
-image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/node:latest
+image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/alpine:latest
```
There are other additional predefined CI/CD variables you can also use:
@@ -125,13 +127,20 @@ To store a Docker image in Dependency Proxy storage:
1. Go to your group's **Packages & Registries > Dependency Proxy**.
1. Copy the **Dependency Proxy URL**.
1. Use one of these commands. In these examples, the image is `alpine:latest`.
+1. You can also pull images by digest to specify exactly which version of an image to pull.
- - Add the URL to your [`.gitlab-ci.yml`](../../../ci/yaml/README.md#image) file:
+ - Pull an image by tag by adding the image to your [`.gitlab-ci.yml`](../../../ci/yaml/README.md#image) file:
```shell
image: gitlab.example.com/groupname/dependency_proxy/containers/alpine:latest
```
+ - Pull an image by digest by adding the image to your [`.gitlab-ci.yml`](../../../ci/yaml/README.md#image) file:
+
+ ```shell
+ image: ${CI_DEPENDENCY_PROXY_GROUP_IMAGE_PREFIX}/alpine@sha256:c9375e662992791e3f39e919b26f510e5254b42792519c180aad254e6b38f4dc
+ ```
+
- Manually pull the Docker image:
```shell
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index a8b1cdab021..26fa00d6186 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -137,6 +137,10 @@ module API
end
end
# rubocop: enable CodeReuse/ActiveRecord
+
+ def authorize_group_creation!
+ authorize! :create_group
+ end
end
resource :groups do
@@ -169,7 +173,7 @@ module API
if parent_group
authorize! :create_subgroup, parent_group
else
- authorize! :create_group
+ authorize_group_creation!
end
group = create_group
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 0d9b76f3d34..5dc3f71329d 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -629,6 +629,9 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def usage_activity_by_stage_monitor(time_period)
+ # Calculate histogram only for overall as other time periods aren't available/useful here.
+ integrations_histogram = time_period.empty? ? histogram(::AlertManagement::HttpIntegration.active, :project_id, buckets: 1..100) : nil
+
{
clusters: distinct_count(::Clusters::Cluster.where(time_period), :user_id),
clusters_applications_prometheus: cluster_applications_user_distinct_count(::Clusters::Applications::Prometheus, time_period),
@@ -638,8 +641,9 @@ module Gitlab
projects_with_tracing_enabled: distinct_count(::Project.with_tracing_enabled.where(time_period), :creator_id),
projects_with_error_tracking_enabled: distinct_count(::Project.with_enabled_error_tracking.where(time_period), :creator_id),
projects_with_incidents: distinct_count(::Issue.incident.where(time_period), :project_id),
- projects_with_alert_incidents: distinct_count(::Issue.incident.with_alert_management_alerts.where(time_period), :project_id)
- }
+ projects_with_alert_incidents: distinct_count(::Issue.incident.with_alert_management_alerts.where(time_period), :project_id),
+ projects_with_enabled_alert_integrations_histogram: integrations_histogram
+ }.compact
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/gitlab/utils/usage_data.rb b/lib/gitlab/utils/usage_data.rb
index 005da0f9642..854fc5c917d 100644
--- a/lib/gitlab/utils/usage_data.rb
+++ b/lib/gitlab/utils/usage_data.rb
@@ -39,10 +39,12 @@ module Gitlab
extend self
FALLBACK = -1
+ HISTOGRAM_FALLBACK = { '-1' => -1 }.freeze
DISTRIBUTED_HLL_FALLBACK = -2
ALL_TIME_TIME_FRAME_NAME = "all"
SEVEN_DAYS_TIME_FRAME_NAME = "7d"
TWENTY_EIGHT_DAYS_TIME_FRAME_NAME = "28d"
+ MAX_BUCKET_SIZE = 100
def count(relation, column = nil, batch: true, batch_size: nil, start: nil, finish: nil)
if batch
@@ -87,6 +89,73 @@ module Gitlab
FALLBACK
end
+ # We don't support batching with histograms.
+ # Please avoid using this method on large tables.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/323949.
+ #
+ # rubocop: disable CodeReuse/ActiveRecord
+ def histogram(relation, column, buckets:, bucket_size: buckets.size)
+ # Using lambda to avoid exposing histogram specific methods
+ parameters_valid = lambda do
+ error_message =
+ if buckets.first == buckets.last
+ 'Lower bucket bound cannot equal to upper bucket bound'
+ elsif bucket_size == 0
+ 'Bucket size cannot be zero'
+ elsif bucket_size > MAX_BUCKET_SIZE
+ "Bucket size #{bucket_size} exceeds the limit of #{MAX_BUCKET_SIZE}"
+ end
+
+ return true unless error_message
+
+ exception = ArgumentError.new(error_message)
+ exception.set_backtrace(caller)
+ Gitlab::ErrorTracking.track_and_raise_for_dev_exception(exception)
+
+ false
+ end
+
+ return HISTOGRAM_FALLBACK unless parameters_valid.call
+
+ count_grouped = relation.group(column).select(Arel.star.count.as('count_grouped'))
+ cte = Gitlab::SQL::CTE.new(:count_cte, count_grouped)
+
+ # For example, 9 segements gives 10 buckets
+ bucket_segments = bucket_size - 1
+
+ width_bucket = Arel::Nodes::NamedFunction
+ .new('WIDTH_BUCKET', [cte.table[:count_grouped], buckets.first, buckets.last, bucket_segments])
+ .as('buckets')
+
+ query = cte
+ .table
+ .project(width_bucket, cte.table[:count])
+ .group('buckets')
+ .order('buckets')
+ .with(cte.to_arel)
+
+ # Return the histogram as a Hash because buckets are unique.
+ relation
+ .connection
+ .exec_query(query.to_sql)
+ .rows
+ .to_h
+ # Keys are converted to strings in Usage Ping JSON
+ .stringify_keys
+ rescue ActiveRecord::StatementInvalid => e
+ Gitlab::AppJsonLogger.error(
+ event: 'histogram',
+ relation: relation.table_name,
+ operation: 'histogram',
+ operation_args: [column, buckets.first, buckets.last, bucket_segments],
+ query: query.to_sql,
+ message: e.message
+ )
+
+ HISTOGRAM_FALLBACK
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
def add(*args)
return -1 if args.any?(&:negative?)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 53ba4f7ba29..be8ecd13ecb 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -9637,6 +9637,15 @@ msgstr ""
msgid "DastProfiles|The maximum number of seconds allowed for the site under test to respond to a request."
msgstr ""
+msgid "DastProfiles|This profile is currently being used in a policy."
+msgstr ""
+
+msgid "DastProfiles|This scanner profile is currently being used by a policy. To make edits you must remove it from the active policy."
+msgstr ""
+
+msgid "DastProfiles|This site profile is currently being used by a policy. To make edits you must remove it from the active policy."
+msgstr ""
+
msgid "DastProfiles|Turn on AJAX spider"
msgstr ""
@@ -22501,6 +22510,9 @@ msgstr ""
msgid "Pipeline|Failed"
msgstr ""
+msgid "Pipeline|In progress"
+msgstr ""
+
msgid "Pipeline|Key"
msgstr ""
diff --git a/package.json b/package.json
index 56b54b74047..27d7d711fb8 100644
--- a/package.json
+++ b/package.json
@@ -96,7 +96,7 @@
"deckar01-task_list": "^2.3.1",
"diff": "^3.4.0",
"document-register-element": "1.14.3",
- "dompurify": "^2.2.6",
+ "dompurify": "^2.2.7",
"dropzone": "^4.2.0",
"editorconfig": "^0.15.3",
"emoji-regex": "^7.0.3",
diff --git a/qa/qa/page/admin/settings/component/ip_limits.rb b/qa/qa/page/admin/settings/component/ip_limits.rb
index 9db2ae8ba58..1f9bd113cab 100644
--- a/qa/qa/page/admin/settings/component/ip_limits.rb
+++ b/qa/qa/page/admin/settings/component/ip_limits.rb
@@ -14,9 +14,9 @@ module QA
end
def enable_throttles
- check_element :throttle_unauthenticated_checkbox
- check_element :throttle_authenticated_api_checkbox
- check_element :throttle_authenticated_web_checkbox
+ check_element(:throttle_unauthenticated_checkbox)
+ check_element(:throttle_authenticated_api_checkbox)
+ check_element(:throttle_authenticated_web_checkbox)
end
def save_settings
diff --git a/qa/qa/page/admin/settings/component/outbound_requests.rb b/qa/qa/page/admin/settings/component/outbound_requests.rb
index 248ea5b6715..c812c05f9c1 100644
--- a/qa/qa/page/admin/settings/component/outbound_requests.rb
+++ b/qa/qa/page/admin/settings/component/outbound_requests.rb
@@ -19,7 +19,7 @@ module QA
private
def check_allow_requests_to_local_network_from_services_checkbox
- check_element :allow_requests_from_services_checkbox
+ check_element(:allow_requests_from_services_checkbox)
end
def click_save_changes_button
diff --git a/qa/qa/page/admin/settings/component/performance_bar.rb b/qa/qa/page/admin/settings/component/performance_bar.rb
index bc29efb64c0..9e92fa362fb 100644
--- a/qa/qa/page/admin/settings/component/performance_bar.rb
+++ b/qa/qa/page/admin/settings/component/performance_bar.rb
@@ -12,7 +12,7 @@ module QA
end
def enable_performance_bar
- click_element :enable_performance_bar_checkbox
+ check_element(:enable_performance_bar_checkbox)
Capybara.current_session.driver.browser.manage.add_cookie(name: 'perf_bar_enabled', value: 'true')
end
diff --git a/qa/qa/page/admin/settings/component/sign_up_restrictions.rb b/qa/qa/page/admin/settings/component/sign_up_restrictions.rb
index 9526faf4126..9909155641f 100644
--- a/qa/qa/page/admin/settings/component/sign_up_restrictions.rb
+++ b/qa/qa/page/admin/settings/component/sign_up_restrictions.rb
@@ -13,13 +13,13 @@ module QA
end
def require_admin_approval_after_user_signup
- check_element :require_admin_approval_after_user_signup_checkbox
- click_element :save_changes_button
+ check_element(:require_admin_approval_after_user_signup_checkbox)
+ click_element(:save_changes_button)
end
def disable_signups
- uncheck_element :signup_enabled_checkbox
- click_element :save_changes_button
+ uncheck_element(:signup_enabled_checkbox)
+ click_element(:save_changes_button)
end
end
end
diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb
index 99f73bbba48..d1b556b58fb 100644
--- a/qa/qa/page/base.rb
+++ b/qa/qa/page/base.rb
@@ -133,9 +133,15 @@ module QA
end
def check_element(name)
+ if find_element(name, visible: false).checked?
+ QA::Runtime::Logger.debug("#{name} is already checked")
+
+ return
+ end
+
retry_until(sleep_interval: 1) do
- find_element(name).set(true)
- checked = find_element(name).checked?
+ find_element(name, visible: false).click
+ checked = find_element(name, visible: false).checked?
QA::Runtime::Logger.debug(checked ? "#{name} was checked" : "#{name} was not checked")
@@ -144,10 +150,19 @@ module QA
end
def uncheck_element(name)
+ unless find_element(name, visible: false).checked?
+ QA::Runtime::Logger.debug("#{name} is already unchecked")
+
+ return
+ end
+
retry_until(sleep_interval: 1) do
- find_element(name).set(false)
+ find_element(name, visible: false).click
+ unchecked = !find_element(name, visible: false).checked?
+
+ QA::Runtime::Logger.debug(unchecked ? "#{name} was unchecked" : "#{name} was not unchecked")
- !find_element(name).checked?
+ unchecked
end
end
diff --git a/qa/qa/page/group/settings/general.rb b/qa/qa/page/group/settings/general.rb
index 8f5267c3362..ced8bd5c812 100644
--- a/qa/qa/page/group/settings/general.rb
+++ b/qa/qa/page/group/settings/general.rb
@@ -54,57 +54,57 @@ module QA
end
def set_lfs_enabled
- expand_content :permission_lfs_2fa_content
- check_element :lfs_checkbox
- click_element :save_permissions_changes_button
+ expand_content(:permission_lfs_2fa_content)
+ check_element(:lfs_checkbox)
+ click_element(:save_permissions_changes_button)
end
def set_lfs_disabled
- expand_content :permission_lfs_2fa_content
- uncheck_element :lfs_checkbox
- click_element :save_permissions_changes_button
+ expand_content(:permission_lfs_2fa_content)
+ uncheck_element(:lfs_checkbox)
+ click_element(:save_permissions_changes_button)
end
def set_request_access_enabled
- expand_content :permission_lfs_2fa_content
- check_element :request_access_checkbox
- click_element :save_permissions_changes_button
+ expand_content(:permission_lfs_2fa_content)
+ check_element(:request_access_checkbox)
+ click_element(:save_permissions_changes_button)
end
def set_request_access_disabled
- expand_content :permission_lfs_2fa_content
- uncheck_element :request_access_checkbox
- click_element :save_permissions_changes_button
+ expand_content(:permission_lfs_2fa_content)
+ uncheck_element(:request_access_checkbox)
+ click_element(:save_permissions_changes_button)
end
def set_require_2fa_enabled
- expand_content :permission_lfs_2fa_content
- check_element :require_2fa_checkbox
- click_element :save_permissions_changes_button
+ expand_content(:permission_lfs_2fa_content)
+ check_element(:require_2fa_checkbox)
+ click_element(:save_permissions_changes_button)
end
def set_require_2fa_disabled
- expand_content :permission_lfs_2fa_content
- uncheck_element :require_2fa_checkbox
- click_element :save_permissions_changes_button
+ expand_content(:permission_lfs_2fa_content)
+ uncheck_element(:require_2fa_checkbox)
+ click_element(:save_permissions_changes_button)
end
def set_project_creation_level(value)
- expand_content :permission_lfs_2fa_content
+ expand_content(:permission_lfs_2fa_content)
select_element(:project_creation_level_dropdown, value)
- click_element :save_permissions_changes_button
+ click_element(:save_permissions_changes_button)
end
def toggle_request_access
- expand_content :permission_lfs_2fa_content
+ expand_content(:permission_lfs_2fa_content)
if find_element(:request_access_checkbox).checked?
- uncheck_element :request_access_checkbox
+ uncheck_element(:request_access_checkbox)
else
- check_element :request_access_checkbox
+ check_element(:request_access_checkbox)
end
- click_element :save_permissions_changes_button
+ click_element(:save_permissions_changes_button)
end
end
end
diff --git a/qa/qa/page/merge_request/show.rb b/qa/qa/page/merge_request/show.rb
index 056942d0eaf..89b6effcad7 100644
--- a/qa/qa/page/merge_request/show.rb
+++ b/qa/qa/page/merge_request/show.rb
@@ -228,6 +228,7 @@ module QA
!find_element(:squash_checkbox).disabled?
end
+ # TODO: Fix workaround for data-qa-selector failure
click_element(:squash_checkbox)
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index 7e296528795..d1033dbfca9 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -68,7 +68,7 @@ module QA
end
def enable_initialize_with_readme
- check_element :initialize_with_readme_checkbox
+ check_element(:initialize_with_readme_checkbox)
end
end
end
diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb
index 1b9a451c47d..59f59ca9966 100644
--- a/qa/qa/page/project/operations/kubernetes/add_existing.rb
+++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb
@@ -36,7 +36,7 @@ module QA
end
def uncheck_rbac!
- uncheck_element :rbac_checkbox
+ uncheck_element(:rbac_checkbox)
end
end
end
diff --git a/qa/qa/page/project/settings/auto_devops.rb b/qa/qa/page/project/settings/auto_devops.rb
index 827d5b072c3..9dffa010805 100644
--- a/qa/qa/page/project/settings/auto_devops.rb
+++ b/qa/qa/page/project/settings/auto_devops.rb
@@ -11,8 +11,8 @@ module QA
end
def enable_autodevops
- check_element :enable_autodevops_checkbox
- click_element :save_changes_button
+ check_element(:enable_autodevops_checkbox)
+ click_element(:save_changes_button)
end
end
end
diff --git a/qa/qa/page/project/settings/ci_variables.rb b/qa/qa/page/project/settings/ci_variables.rb
index f2ced668a60..2b8fad64afb 100644
--- a/qa/qa/page/project/settings/ci_variables.rb
+++ b/qa/qa/page/project/settings/ci_variables.rb
@@ -10,7 +10,6 @@ module QA
view 'app/assets/javascripts/ci_variable_list/components/ci_variable_modal.vue' do
element :ci_variable_key_field
element :ci_variable_value_field
- element :ci_variable_masked_checkbox
element :ci_variable_save_button
element :ci_variable_delete_button
end
diff --git a/qa/qa/page/project/settings/incidents.rb b/qa/qa/page/project/settings/incidents.rb
index 9b523e2aa9e..610129851d9 100644
--- a/qa/qa/page/project/settings/incidents.rb
+++ b/qa/qa/page/project/settings/incidents.rb
@@ -13,7 +13,7 @@ module QA
end
def enable_issues_for_incidents
- check_element :create_issue_checkbox
+ check_element(:create_issue_checkbox)
end
def select_issue_template(template)
diff --git a/qa/qa/page/project/settings/merge_request.rb b/qa/qa/page/project/settings/merge_request.rb
index 34754124931..fe5d629effe 100644
--- a/qa/qa/page/project/settings/merge_request.rb
+++ b/qa/qa/page/project/settings/merge_request.rb
@@ -29,7 +29,7 @@ module QA
end
def enable_merge_if_all_disscussions_are_resolved
- click_element :allow_merge_if_all_discussions_are_resolved_checkbox
+ check_element(:allow_merge_if_all_discussions_are_resolved_checkbox)
click_save_changes
end
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 45c46004790..fd68ac0de16 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -44,10 +44,6 @@ module QA
element :commit_button
end
- view 'app/assets/javascripts/ide/components/commit_sidebar/new_merge_request_option.vue' do
- element :start_new_mr_checkbox
- end
-
view 'app/assets/javascripts/ide/components/repo_editor.vue' do
element :editor_container
end
diff --git a/rubocop/rubocop-usage-data.yml b/rubocop/rubocop-usage-data.yml
index d0dd6a2e4da..a03f21e491a 100644
--- a/rubocop/rubocop-usage-data.yml
+++ b/rubocop/rubocop-usage-data.yml
@@ -34,6 +34,7 @@ UsageData/LargeTable:
CountMethods:
- :count
- :distinct_count
+ - :histogram
AllowedMethods:
- :arel_table
- :minimum
diff --git a/scripts/lint-doc.sh b/scripts/lint-doc.sh
index c94faa8f6f4..4d0c1ba99d2 100755
--- a/scripts/lint-doc.sh
+++ b/scripts/lint-doc.sh
@@ -30,13 +30,13 @@ then
((ERRORCODE++))
fi
-# Test for non-standard spaces (NBSP, NNBSP) in documentation.
+# Test for non-standard spaces (NBSP, NNBSP, ZWSP) in documentation.
echo '=> Checking for non-standard spaces...'
echo
-grep --extended-regexp --binary-file=without-match --recursive '[  ]' doc/ >/dev/null 2>&1
+grep --extended-regexp --binary-file=without-match --recursive '[  ​]' doc/ >/dev/null 2>&1
if [ $? -eq 0 ]
then
- echo '✖ ERROR: Non-standard spaces (NBSP, NNBSP) should not be used in documentation.
+ echo '✖ ERROR: Non-standard spaces (NBSP, NNBSP, ZWSP) should not be used in documentation.
https://docs.gitlab.com/ee/development/documentation/styleguide/index.html#spaces-between-words
Replace with standard spaces:' >&2
# Find the spaces, then add color codes with sed to highlight each NBSP or NNBSP in the output.
diff --git a/spec/frontend/packages/details/store/getters_spec.js b/spec/frontend/packages/details/store/getters_spec.js
index fa7123c813d..f12b75d3b70 100644
--- a/spec/frontend/packages/details/store/getters_spec.js
+++ b/spec/frontend/packages/details/store/getters_spec.js
@@ -250,8 +250,8 @@ describe('Getters PackageDetails Store', () => {
setupState();
expect(gradleGroovyAddSourceCommand(state)).toMatchInlineSnapshot(`
- "gitlab {
- url \\"foo/registry\\"
+ "maven {
+ url 'foo/registry'
}"
`);
});
diff --git a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
index edeab883e91..4c72dad735e 100644
--- a/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
+++ b/spec/frontend/pipelines/graph/linked_pipelines_column_spec.js
@@ -116,7 +116,7 @@ describe('Linked Pipelines Column', () => {
it('emits the error', async () => {
await clickExpandButton();
- expect(wrapper.emitted().error).toEqual([[LOAD_FAILURE]]);
+ expect(wrapper.emitted().error).toEqual([[{ type: LOAD_FAILURE, skipSentry: true }]]);
});
it('does not show the pipeline', async () => {
@@ -167,7 +167,7 @@ describe('Linked Pipelines Column', () => {
it('emits the error', async () => {
await clickExpandButton();
- expect(wrapper.emitted().error).toEqual([[LOAD_FAILURE]]);
+ expect(wrapper.emitted().error).toEqual([[{ type: LOAD_FAILURE, skipSentry: true }]]);
});
it('does not show the pipeline', async () => {
diff --git a/spec/frontend/pipelines/time_ago_spec.js b/spec/frontend/pipelines/time_ago_spec.js
index 4919efbb4c6..93aeb049434 100644
--- a/spec/frontend/pipelines/time_ago_spec.js
+++ b/spec/frontend/pipelines/time_ago_spec.js
@@ -29,6 +29,7 @@ describe('Timeago component', () => {
const duration = () => wrapper.find('.duration');
const finishedAt = () => wrapper.find('.finished-at');
+ const findInProgress = () => wrapper.find('[data-testid="pipeline-in-progress"]');
describe('with duration', () => {
beforeEach(() => {
@@ -77,4 +78,21 @@ describe('Timeago component', () => {
expect(finishedAt().exists()).toBe(false);
});
});
+
+ describe('in progress', () => {
+ it.each`
+ durationTime | finishedAtTime | shouldShow
+ ${10} | ${'2017-04-26T12:40:23.277Z'} | ${false}
+ ${10} | ${''} | ${false}
+ ${0} | ${'2017-04-26T12:40:23.277Z'} | ${false}
+ ${0} | ${''} | ${true}
+ `(
+ 'progress state shown: $shouldShow when pipeline duration is $durationTime and finished_at is $finishedAtTime',
+ ({ durationTime, finishedAtTime, shouldShow }) => {
+ createComponent({ duration: durationTime, finished_at: finishedAtTime });
+
+ expect(findInProgress().exists()).toBe(shouldShow);
+ },
+ );
+ });
});
diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js
index a33401c5ba9..a879b06e858 100644
--- a/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_related_links_spec.js
@@ -1,85 +1,88 @@
-import Vue from 'vue';
-import mountComponent from 'helpers/vue_mount_component_helper';
-import relatedLinksComponent from '~/vue_merge_request_widget/components/mr_widget_related_links.vue';
+import { shallowMount } from '@vue/test-utils';
+import RelatedLinks from '~/vue_merge_request_widget/components/mr_widget_related_links.vue';
describe('MRWidgetRelatedLinks', () => {
- let vm;
+ let wrapper;
- const createComponent = (data) => {
- const Component = Vue.extend(relatedLinksComponent);
-
- return mountComponent(Component, data);
+ const createComponent = (propsData = {}) => {
+ wrapper = shallowMount(RelatedLinks, { propsData });
};
afterEach(() => {
- vm.$destroy();
+ wrapper.destroy();
});
describe('computed', () => {
describe('closesText', () => {
it('returns Closes text for open merge request', () => {
- vm = createComponent({ state: 'open', relatedLinks: {} });
+ createComponent({ state: 'open', relatedLinks: {} });
- expect(vm.closesText).toEqual('Closes');
+ expect(wrapper.vm.closesText).toBe('Closes');
});
it('returns correct text for closed merge request', () => {
- vm = createComponent({ state: 'closed', relatedLinks: {} });
+ createComponent({ state: 'closed', relatedLinks: {} });
- expect(vm.closesText).toEqual('Did not close');
+ expect(wrapper.vm.closesText).toBe('Did not close');
});
it('returns correct tense for merged request', () => {
- vm = createComponent({ state: 'merged', relatedLinks: {} });
+ createComponent({ state: 'merged', relatedLinks: {} });
- expect(vm.closesText).toEqual('Closed');
+ expect(wrapper.vm.closesText).toBe('Closed');
});
});
});
it('should have only have closing issues text', () => {
- vm = createComponent({
+ createComponent({
relatedLinks: {
closing: '<a href="#">#23</a> and <a>#42</a>',
},
});
- const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim();
+ const content = wrapper
+ .text()
+ .replace(/\n(\s)+/g, ' ')
+ .trim();
expect(content).toContain('Closes #23 and #42');
expect(content).not.toContain('Mentions');
});
it('should have only have mentioned issues text', () => {
- vm = createComponent({
+ createComponent({
relatedLinks: {
mentioned: '<a href="#">#7</a>',
},
});
- expect(vm.$el.innerText).toContain('Mentions #7');
- expect(vm.$el.innerText).not.toContain('Closes');
+ expect(wrapper.text().trim()).toContain('Mentions #7');
+ expect(wrapper.text().trim()).not.toContain('Closes');
});
it('should have closing and mentioned issues at the same time', () => {
- vm = createComponent({
+ createComponent({
relatedLinks: {
closing: '<a href="#">#7</a>',
mentioned: '<a href="#">#23</a> and <a>#42</a>',
},
});
- const content = vm.$el.textContent.replace(/\n(\s)+/g, ' ').trim();
+ const content = wrapper
+ .text()
+ .replace(/\n(\s)+/g, ' ')
+ .trim();
expect(content).toContain('Closes #7');
expect(content).toContain('Mentions #23 and #42');
});
it('should have assing issues link', () => {
- vm = createComponent({
+ createComponent({
relatedLinks: {
assignToMe: '<a href="#">Assign yourself to these issues</a>',
},
});
- expect(vm.$el.innerText).toContain('Assign yourself to these issues');
+ expect(wrapper.text().trim()).toContain('Assign yourself to these issues');
});
});
diff --git a/spec/helpers/boards_helper_spec.rb b/spec/helpers/boards_helper_spec.rb
index 83aad206547..b00ee19cea2 100644
--- a/spec/helpers/boards_helper_spec.rb
+++ b/spec/helpers/boards_helper_spec.rb
@@ -4,8 +4,8 @@ require 'spec_helper'
RSpec.describe BoardsHelper do
let_it_be(:user) { create(:user) }
- let_it_be(:project) { create(:project) }
let_it_be(:base_group) { create(:group, path: 'base') }
+ let_it_be(:project) { create(:project, group: base_group) }
let_it_be(:project_board) { create(:board, project: project) }
let_it_be(:group_board) { create(:board, group: base_group) }
@@ -82,6 +82,10 @@ RSpec.describe BoardsHelper do
expect(helper.board_data[:labels_fetch_path]).to eq("/#{project.full_path}/-/labels.json?include_ancestor_groups=true")
expect(helper.board_data[:labels_manage_path]).to eq("/#{project.full_path}/-/labels")
end
+
+ it 'returns the group id of a project' do
+ expect(helper.board_data[:group_id]).to eq(project.group.id)
+ end
end
context 'group board' do
@@ -102,6 +106,10 @@ RSpec.describe BoardsHelper do
expect(helper.board_data[:labels_fetch_path]).to eq("/groups/#{base_group.full_path}/-/labels.json?include_ancestor_groups=true&only_group_labels=true")
expect(helper.board_data[:labels_manage_path]).to eq("/groups/#{base_group.full_path}/-/labels")
end
+
+ it 'returns the group id' do
+ expect(helper.board_data[:group_id]).to eq(base_group.id)
+ end
end
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 97000856626..b1581bf02a6 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -382,14 +382,15 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
describe 'usage_activity_by_stage_monitor' do
it 'includes accurate usage_activity_by_stage data' do
for_defined_days_back do
- user = create(:user, dashboard: 'operations')
+ user = create(:user, dashboard: 'operations')
cluster = create(:cluster, user: user)
- create(:project, creator: user)
+ project = create(:project, creator: user)
create(:clusters_applications_prometheus, :installed, cluster: cluster)
create(:project_tracing_setting)
create(:project_error_tracking_setting)
create(:incident)
create(:incident, alert_management_alert: create(:alert_management_alert))
+ create(:alert_management_http_integration, :active, project: project)
end
expect(described_class.usage_activity_by_stage_monitor({})).to include(
@@ -399,10 +400,12 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
projects_with_tracing_enabled: 2,
projects_with_error_tracking_enabled: 2,
projects_with_incidents: 4,
- projects_with_alert_incidents: 2
+ projects_with_alert_incidents: 2,
+ projects_with_enabled_alert_integrations_histogram: { '1' => 2 }
)
- expect(described_class.usage_activity_by_stage_monitor(described_class.last_28_days_time_period)).to include(
+ data_28_days = described_class.usage_activity_by_stage_monitor(described_class.last_28_days_time_period)
+ expect(data_28_days).to include(
clusters: 1,
clusters_applications_prometheus: 1,
operations_dashboard_default_dashboard: 1,
@@ -411,6 +414,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
projects_with_incidents: 2,
projects_with_alert_incidents: 1
)
+
+ expect(data_28_days).not_to include(:projects_with_enabled_alert_integrations_histogram)
end
end
@@ -528,14 +533,14 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
expect(subject.keys).to include(*UsageDataHelpers::USAGE_DATA_KEYS)
end
- it 'gathers usage counts' do
+ it 'gathers usage counts', :aggregate_failures do
count_data = subject[:counts]
expect(count_data[:boards]).to eq(1)
expect(count_data[:projects]).to eq(4)
- expect(count_data.values_at(*UsageDataHelpers::SMAU_KEYS)).to all(be_an(Integer))
expect(count_data.keys).to include(*UsageDataHelpers::COUNTS_KEYS)
expect(UsageDataHelpers::COUNTS_KEYS - count_data.keys).to be_empty
+ expect(count_data.values).to all(be_a_kind_of(Integer))
end
it 'gathers usage counts correctly' do
diff --git a/spec/lib/gitlab/utils/usage_data_spec.rb b/spec/lib/gitlab/utils/usage_data_spec.rb
index 45e57428141..6e1904c43e1 100644
--- a/spec/lib/gitlab/utils/usage_data_spec.rb
+++ b/spec/lib/gitlab/utils/usage_data_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Gitlab::Utils::UsageData do
+ include Database::DatabaseHelpers
+
describe '#count' do
let(:relation) { double(:relation) }
@@ -183,6 +185,102 @@ RSpec.describe Gitlab::Utils::UsageData do
end
end
+ describe '#histogram' do
+ let_it_be(:projects) { create_list(:project, 3) }
+ let(:project1) { projects.first }
+ let(:project2) { projects.second }
+ let(:project3) { projects.third }
+
+ let(:fallback) { described_class::HISTOGRAM_FALLBACK }
+ let(:relation) { AlertManagement::HttpIntegration.active }
+ let(:column) { :project_id }
+
+ def expect_error(exception, message, &block)
+ expect(Gitlab::ErrorTracking)
+ .to receive(:track_and_raise_for_dev_exception)
+ .with(instance_of(exception))
+ .and_call_original
+
+ expect(&block).to raise_error(
+ an_instance_of(exception).and(
+ having_attributes(message: message, backtrace: be_kind_of(Array)))
+ )
+ end
+
+ it 'checks bucket bounds to be not equal' do
+ expect_error(ArgumentError, 'Lower bucket bound cannot equal to upper bucket bound') do
+ described_class.histogram(relation, column, buckets: 1..1)
+ end
+ end
+
+ it 'checks bucket_size being non-zero' do
+ expect_error(ArgumentError, 'Bucket size cannot be zero') do
+ described_class.histogram(relation, column, buckets: 1..2, bucket_size: 0)
+ end
+ end
+
+ it 'limits the amount of buckets without providing bucket_size argument' do
+ expect_error(ArgumentError, 'Bucket size 101 exceeds the limit of 100') do
+ described_class.histogram(relation, column, buckets: 1..101)
+ end
+ end
+
+ it 'limits the amount of buckets when providing bucket_size argument' do
+ expect_error(ArgumentError, 'Bucket size 101 exceeds the limit of 100') do
+ described_class.histogram(relation, column, buckets: 1..2, bucket_size: 101)
+ end
+ end
+
+ it 'without data' do
+ histogram = described_class.histogram(relation, column, buckets: 1..100)
+
+ expect(histogram).to eq({})
+ end
+
+ it 'aggregates properly within bounds' do
+ create(:alert_management_http_integration, :active, project: project1)
+ create(:alert_management_http_integration, :inactive, project: project1)
+
+ create(:alert_management_http_integration, :active, project: project2)
+ create(:alert_management_http_integration, :active, project: project2)
+ create(:alert_management_http_integration, :inactive, project: project2)
+
+ create(:alert_management_http_integration, :active, project: project3)
+ create(:alert_management_http_integration, :inactive, project: project3)
+
+ histogram = described_class.histogram(relation, column, buckets: 1..100)
+
+ expect(histogram).to eq('1' => 2, '2' => 1)
+ end
+
+ it 'aggregates properly out of bounds' do
+ create_list(:alert_management_http_integration, 3, :active, project: project1)
+ histogram = described_class.histogram(relation, column, buckets: 1..2)
+
+ expect(histogram).to eq('2' => 1)
+ end
+
+ it 'returns fallback and logs canceled queries' do
+ create(:alert_management_http_integration, :active, project: project1)
+
+ expect(Gitlab::AppJsonLogger).to receive(:error).with(
+ event: 'histogram',
+ relation: relation.table_name,
+ operation: 'histogram',
+ operation_args: [column, 1, 100, 99],
+ query: kind_of(String),
+ message: /PG::QueryCanceled/
+ )
+
+ with_statement_timeout(0.001) do
+ relation = AlertManagement::HttpIntegration.select('pg_sleep(0.002)')
+ histogram = described_class.histogram(relation, column, buckets: 1..100)
+
+ expect(histogram).to eq(fallback)
+ end
+ end
+ end
+
describe '#add' do
it 'adds given values' do
expect(described_class.add(1, 3)).to eq(4)
diff --git a/spec/support/helpers/database/database_helpers.rb b/spec/support/helpers/database/database_helpers.rb
index b8d7ea3662f..db093bcef85 100644
--- a/spec/support/helpers/database/database_helpers.rb
+++ b/spec/support/helpers/database/database_helpers.rb
@@ -5,11 +5,65 @@ module Database
# In order to directly work with views using factories,
# we can swapout the view for a table of identical structure.
def swapout_view_for_table(view)
- ActiveRecord::Base.connection.execute(<<~SQL)
+ ActiveRecord::Base.connection.execute(<<~SQL.squish)
CREATE TABLE #{view}_copy (LIKE #{view});
DROP VIEW #{view};
ALTER TABLE #{view}_copy RENAME TO #{view};
SQL
end
+
+ # Set statement timeout temporarily.
+ # Useful when testing query timeouts.
+ #
+ # Note that this method cannot restore the timeout if a query
+ # was canceled due to e.g. a statement timeout.
+ # Refrain from using this transaction in these situations.
+ #
+ # @param timeout - Statement timeout in seconds
+ #
+ # Example:
+ #
+ # with_statement_timeout(0.1) do
+ # model.select('pg_sleep(0.11)')
+ # end
+ def with_statement_timeout(timeout)
+ # Force a positive value and a minimum of 1ms for very small values.
+ timeout = (timeout * 1000).abs.ceil
+
+ raise ArgumentError, 'Using a timeout of `0` means to disable statement timeout.' if timeout == 0
+
+ previous_timeout = ActiveRecord::Base.connection
+ .exec_query('SHOW statement_timeout')[0].fetch('statement_timeout')
+
+ set_statement_timeout("#{timeout}ms")
+
+ yield
+ ensure
+ begin
+ set_statement_timeout(previous_timeout)
+ rescue ActiveRecord::StatementInvalid
+ # After a transaction was canceled/aborted due to e.g. a statement
+ # timeout commands are ignored and will raise in PG::InFailedSqlTransaction.
+ # We can safely ignore this error because the statement timeout was set
+ # for the currrent transaction which will be closed anyway.
+ end
+ end
+
+ # Set statement timeout for the current transaction.
+ #
+ # Note, that it does not restore the previous statement timeout.
+ # Use `with_statement_timeout` instead.
+ #
+ # @param timeout - Statement timeout in seconds
+ #
+ # Example:
+ #
+ # set_statement_timeout(0.1)
+ # model.select('pg_sleep(0.11)')
+ def set_statement_timeout(timeout)
+ ActiveRecord::Base.connection.execute(
+ format(%(SET LOCAL statement_timeout = '%s'), timeout)
+ )
+ end
end
end
diff --git a/yarn.lock b/yarn.lock
index 2d02295121b..dd63db5131d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4308,10 +4308,10 @@ domhandler@^2.3.0:
dependencies:
domelementtype "1"
-dompurify@^2.2.6:
- version "2.2.6"
- resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.6.tgz#54945dc5c0b45ce5ae228705777e8e59d7b2edc4"
- integrity sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ==
+dompurify@^2.2.6, dompurify@^2.2.7:
+ version "2.2.7"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.2.7.tgz#a5f055a2a471638680e779bd08fc334962d11fd8"
+ integrity sha512-jdtDffdGNY+C76jvodNTu9jt5yYj59vuTUyx+wXdzcSwAGTYZDAQkQ7Iwx9zcGrA4ixC1syU4H3RZROqRxokxg==
domutils@^1.5.1:
version "1.7.0"