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:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-08-12 09:09:53 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2020-08-12 09:09:53 +0300
commit90156f527b43a15e794c3351ecfc59aff42c440a (patch)
treea59318246adf4297b3911f442b6a789fd38d35a6
parent737684a392db1178770ad5b1d20b64386aadcac5 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop.yml5
-rw-r--r--.rubocop_todo.yml60
-rw-r--r--app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue21
-rw-r--r--app/assets/javascripts/monitoring/components/alert_widget.vue8
-rw-r--r--app/assets/javascripts/monitoring/components/alert_widget_form.vue32
-rw-r--r--app/assets/javascripts/monitoring/services/alerts_service.js25
-rw-r--r--app/assets/javascripts/monitoring/validators.js13
-rw-r--r--app/controllers/concerns/issuable_collections.rb2
-rw-r--r--app/controllers/concerns/paginated_collection.rb2
-rw-r--r--app/controllers/projects/environments_controller.rb1
-rw-r--r--app/graphql/resolvers/board_list_issues_resolver.rb5
-rw-r--r--app/helpers/graph_helper.rb2
-rw-r--r--app/helpers/timeboxes_helper.rb4
-rw-r--r--app/models/audit_event.rb2
-rw-r--r--app/models/ci/pipeline.rb6
-rw-r--r--app/models/ci/stage.rb2
-rw-r--r--app/models/concerns/update_project_statistics.rb2
-rw-r--r--app/models/merge_request_diff.rb2
-rw-r--r--app/models/milestone.rb2
-rw-r--r--app/models/network/graph.rb8
-rw-r--r--app/models/postgresql/replication_slot.rb2
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/suggestion.rb2
-rw-r--r--app/models/user.rb2
-rw-r--r--app/serializers/merge_request_widget_entity.rb2
-rw-r--r--app/services/boards/issues/move_service.rb2
-rw-r--r--app/services/cohorts_service.rb2
-rw-r--r--app/services/discussions/resolve_service.rb2
-rw-r--r--app/services/issues/reorder_service.rb2
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/services/packages/nuget/metadata_extraction_service.rb2
-rw-r--r--app/services/packages/nuget/search_service.rb4
-rw-r--r--app/services/projects/auto_devops/disable_service.rb2
-rw-r--r--app/services/projects/update_pages_service.rb2
-rw-r--r--app/services/search_service.rb2
-rw-r--r--app/workers/admin_email_worker.rb2
-rw-r--r--app/workers/gitlab/import/advance_stage.rb2
-rw-r--r--changelogs/unreleased/220322-refactor-target-type.yml5
-rw-r--r--changelogs/unreleased/224457-fix-data-integrity-on-issue_feedback-issue_links.yml5
-rw-r--r--changelogs/unreleased/229548-adjust-telemetry-code-for-incidents.yml5
-rw-r--r--config/initializers/validate_puma.rb2
-rw-r--r--db/post_migrate/20200807110237_add_migration_index_to_vulnerabilities_occurrences.rb20
-rw-r--r--db/post_migrate/20200810100921_add_target_type_to_audit_event.rb121
-rw-r--r--db/post_migrate/20200810101029_add_text_limit_to_audit_event_target_type.rb20
-rw-r--r--db/schema_migrations/202008071102371
-rw-r--r--db/schema_migrations/202008101009211
-rw-r--r--db/schema_migrations/202008101010291
-rw-r--r--db/structure.sql9
-rw-r--r--doc/api/namespaces.md49
-rw-r--r--doc/operations/metrics/dashboards/img/metrics_dashboard_panel_preview_v13_3.pngbin0 -> 67972 bytes
-rw-r--r--doc/operations/metrics/dashboards/index.md45
-rw-r--r--doc/user/analytics/img/vsa_filter_bar_v13.3.pngbin0 -> 117834 bytes
-rw-r--r--doc/user/analytics/value_stream_analytics.md25
-rw-r--r--lib/gitlab/metrics/dashboard/validator/client.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/validator/errors.rb41
-rw-r--r--lib/gitlab/usage_data.rb5
-rw-r--r--locale/gitlab.pot82
-rw-r--r--spec/controllers/projects/ci/daily_build_group_report_results_controller_spec.rb4
-rw-r--r--spec/factories/usage_data.rb4
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_groups_missing_panels_and_group.yml33
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_is_an_array.yml15
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_missing_panel_groups.yml32
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_panel_is_missing_metrics.yml15
-rw-r--r--spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_panle_groups_wrong_content_type.yml33
-rw-r--r--spec/frontend/monitoring/components/alert_widget_form_spec.js111
-rw-r--r--spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb130
-rw-r--r--spec/lib/gitlab/metrics/dashboard/validator_spec.rb44
67 files changed, 660 insertions, 440 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 9fb46e87c34..6529fc88e1e 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -443,6 +443,11 @@ RSpec/HaveGitlabHttpStatus:
Style/MultilineWhenThen:
Enabled: false
+# We use EnforcedStyle of comparison here due to it being better
+# performing code as seen in https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36221#note_375659681
+Style/NumericPredicate:
+ EnforcedStyle: comparison
+
Style/FloatDivision:
Enabled: false
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 67ed227a284..21784a993b1 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -596,66 +596,6 @@ Style/Next:
Style/NumericLiteralPrefix:
Enabled: false
-# Offense count: 130
-# Cop supports --auto-correct.
-# Configuration parameters: AutoCorrect, EnforcedStyle, IgnoredMethods.
-# SupportedStyles: predicate, comparison
-# We use EnforcedStyle of comparison here due to it being better
-# performing code as seen https://gitlab.com/gitlab-org/gitlab/-/merge_requests/36221#note_375659681
-Style/NumericPredicate:
- EnforcedStyle: comparison
- Exclude:
- - 'app/controllers/concerns/issuable_collections.rb'
- - 'app/controllers/concerns/paginated_collection.rb'
- - 'app/helpers/graph_helper.rb'
- - 'app/helpers/timeboxes_helper.rb'
- - 'app/models/ci/pipeline.rb'
- - 'app/models/ci/stage.rb'
- - 'app/models/concerns/update_project_statistics.rb'
- - 'app/models/merge_request_diff.rb'
- - 'app/models/milestone.rb'
- - 'app/models/network/graph.rb'
- - 'app/models/postgresql/replication_slot.rb'
- - 'app/models/project.rb'
- - 'app/models/suggestion.rb'
- - 'app/models/user.rb'
- - 'app/serializers/merge_request_widget_entity.rb'
- - 'app/services/boards/issues/move_service.rb'
- - 'app/services/cohorts_service.rb'
- - 'app/services/discussions/resolve_service.rb'
- - 'app/services/issues/reorder_service.rb'
- - 'app/services/notes/create_service.rb'
- - 'app/services/packages/nuget/metadata_extraction_service.rb'
- - 'app/services/packages/nuget/search_service.rb'
- - 'app/services/projects/auto_devops/disable_service.rb'
- - 'app/services/projects/update_pages_service.rb'
- - 'app/services/search_service.rb'
- - 'app/workers/admin_email_worker.rb'
- - 'app/workers/gitlab/import/advance_stage.rb'
- - 'config/initializers/validate_puma.rb'
- - 'ee/app/controllers/security/projects_controller.rb'
- - 'ee/app/graphql/mutations/instance_security_dashboard/remove_project.rb'
- - 'ee/app/helpers/ee/timeboxes_helper.rb'
- - 'ee/app/models/ci/minutes/quota.rb'
- - 'ee/app/models/ee/ci/runner.rb'
- - 'ee/app/models/geo_node_status.rb'
- - 'ee/app/models/license.rb'
- - 'ee/app/models/namespace_statistics.rb'
- - 'ee/app/services/ee/issues/base_service.rb'
- - 'ee/app/services/ee/merge_requests/approval_service.rb'
- - 'ee/app/services/ee/quick_actions/target_service.rb'
- - 'ee/app/services/elastic/indexing_control_service.rb'
- - 'ee/app/services/geo/hashed_storage_migration_service.rb'
- - 'ee/app/services/geo/prune_event_log_service.rb'
- - 'ee/app/services/security/waf_anomaly_summary_service.rb'
- - 'ee/app/services/update_build_minutes_service.rb'
- - 'ee/app/workers/geo/container_repository_sync_dispatch_worker.rb'
- - 'ee/app/workers/geo/file_download_dispatch_worker.rb'
- - 'ee/app/workers/geo/registry_sync_worker.rb'
- - 'ee/app/workers/geo/repository_shard_sync_worker.rb'
- - 'ee/app/workers/geo/repository_verification/primary/shard_worker.rb'
- - 'ee/app/models/ee/project.rb'
-
# Offense count: 117
# Cop supports --auto-correct.
Style/ParallelAssignment:
diff --git a/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue b/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue
index 7b703c5ede1..5002a8fe85d 100644
--- a/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue
+++ b/app/assets/javascripts/ci_variable_list/components/ci_variable_table.vue
@@ -1,5 +1,5 @@
<script>
-import { GlTable, GlDeprecatedButton, GlModalDirective, GlIcon } from '@gitlab/ui';
+import { GlTable, GlButton, GlModalDirective, GlIcon } from '@gitlab/ui';
import { s__, __ } from '~/locale';
import { mapState, mapActions } from 'vuex';
import { ADD_CI_VARIABLE_MODAL_ID } from '../constants';
@@ -51,7 +51,7 @@ export default {
],
components: {
GlTable,
- GlDeprecatedButton,
+ GlButton,
GlIcon,
CiVariablePopover,
},
@@ -147,14 +147,14 @@ export default {
</div>
</template>
<template #cell(actions)="{ item }">
- <gl-deprecated-button
+ <gl-button
ref="edit-ci-variable"
v-gl-modal-directive="$options.modalId"
+ icon="pencil"
+ :aria-label="__('Edit')"
data-qa-selector="edit_ci_variable_button"
@click="editVariable(item)"
- >
- <gl-icon :size="$options.iconSize" name="pencil" />
- </gl-deprecated-button>
+ />
</template>
<template #empty>
<p ref="empty-variables" class="text-center empty-variables text-plain">
@@ -166,20 +166,21 @@ export default {
class="ci-variable-actions d-flex justify-content-end"
:class="{ 'justify-content-center': !tableIsNotEmpty }"
>
- <gl-deprecated-button
+ <gl-button
v-if="tableIsNotEmpty"
ref="secret-value-reveal-button"
data-qa-selector="reveal_ci_variable_value_button"
class="gl-mr-3"
@click="toggleValues(!valuesHidden)"
- >{{ valuesButtonText }}</gl-deprecated-button
+ >{{ valuesButtonText }}</gl-button
>
- <gl-deprecated-button
+ <gl-button
ref="add-ci-variable"
v-gl-modal-directive="$options.modalId"
data-qa-selector="add_ci_variable_button"
variant="success"
- >{{ __('Add Variable') }}</gl-deprecated-button
+ category="primary"
+ >{{ __('Add Variable') }}</gl-button
>
</div>
</div>
diff --git a/app/assets/javascripts/monitoring/components/alert_widget.vue b/app/assets/javascripts/monitoring/components/alert_widget.vue
index 5562981fe1c..3476b180699 100644
--- a/app/assets/javascripts/monitoring/components/alert_widget.vue
+++ b/app/assets/javascripts/monitoring/components/alert_widget.vue
@@ -174,8 +174,8 @@ export default {
handleSetApiAction(apiAction) {
this.apiAction = apiAction;
},
- handleCreate({ operator, threshold, prometheus_metric_id }) {
- const newAlert = { operator, threshold, prometheus_metric_id };
+ handleCreate({ operator, threshold, prometheus_metric_id, runbookUrl }) {
+ const newAlert = { operator, threshold, prometheus_metric_id, runbookUrl };
this.isLoading = true;
this.service
.createAlert(newAlert)
@@ -189,8 +189,8 @@ export default {
this.isLoading = false;
});
},
- handleUpdate({ alert, operator, threshold }) {
- const updatedAlert = { operator, threshold };
+ handleUpdate({ alert, operator, threshold, runbookUrl }) {
+ const updatedAlert = { operator, threshold, runbookUrl };
this.isLoading = true;
this.service
.updateAlert(alert, updatedAlert)
diff --git a/app/assets/javascripts/monitoring/components/alert_widget_form.vue b/app/assets/javascripts/monitoring/components/alert_widget_form.vue
index 60558c0ba14..ef9dfce4aa9 100644
--- a/app/assets/javascripts/monitoring/components/alert_widget_form.vue
+++ b/app/assets/javascripts/monitoring/components/alert_widget_form.vue
@@ -88,6 +88,7 @@ export default {
operator: null,
threshold: null,
prometheusMetricId: null,
+ runbookUrl: null,
selectedAlert: {},
alertQuery: '',
};
@@ -116,7 +117,8 @@ export default {
this.operator &&
this.threshold === Number(this.threshold) &&
(this.operator !== this.selectedAlert.operator ||
- this.threshold !== this.selectedAlert.threshold)
+ this.threshold !== this.selectedAlert.threshold ||
+ this.runbookUrl !== this.selectedAlert.runbookUrl)
);
},
submitAction() {
@@ -153,13 +155,17 @@ export default {
const existingAlert = this.alertsToManage[existingAlertPath];
if (existingAlert) {
+ const { operator, threshold, runbookUrl } = existingAlert;
+
this.selectedAlert = existingAlert;
- this.operator = existingAlert.operator;
- this.threshold = existingAlert.threshold;
+ this.operator = operator;
+ this.threshold = threshold;
+ this.runbookUrl = runbookUrl;
} else {
this.selectedAlert = {};
this.operator = this.operators.greaterThan;
this.threshold = null;
+ this.runbookUrl = null;
}
this.prometheusMetricId = queryId;
@@ -168,13 +174,13 @@ export default {
this.resetAlertData();
this.$emit('cancel');
},
- handleSubmit(e) {
- e.preventDefault();
+ handleSubmit() {
this.$emit(this.submitAction, {
alert: this.selectedAlert.alert_path,
operator: this.operator,
threshold: this.threshold,
prometheus_metric_id: this.prometheusMetricId,
+ runbookUrl: this.runbookUrl,
});
},
handleShown() {
@@ -189,6 +195,7 @@ export default {
this.threshold = null;
this.prometheusMetricId = null;
this.selectedAlert = {};
+ this.runbookUrl = null;
},
getAlertFormActionTrackingOption() {
const label = `${this.submitAction}_alert`;
@@ -217,7 +224,7 @@ export default {
:modal-id="modalId"
:ok-variant="submitAction === 'delete' ? 'danger' : 'success'"
:ok-disabled="formDisabled"
- @ok="handleSubmit"
+ @ok.prevent="handleSubmit"
@hidden="handleHidden"
@shown="handleShown"
>
@@ -259,7 +266,7 @@ export default {
</gl-deprecated-dropdown-item>
</gl-deprecated-dropdown>
</gl-form-group>
- <gl-button-group class="mb-2" :label="s__('PrometheusAlerts|Operator')">
+ <gl-button-group class="mb-3" :label="s__('PrometheusAlerts|Operator')">
<gl-deprecated-button
:class="{ active: operator === operators.greaterThan }"
:disabled="formDisabled"
@@ -296,11 +303,16 @@ export default {
</gl-form-group>
<gl-form-group
v-if="glFeatures.alertRunbooks"
- :label="s__('PrometheusAlerts|Runbook')"
+ :label="s__('PrometheusAlerts|Runbook URL (optional)')"
label-for="alert-runbook"
- data-testid="alertRunbookField"
>
- <gl-form-input id="alert-runbook" :disabled="formDisabled" type="text" />
+ <gl-form-input
+ id="alert-runbook"
+ v-model="runbookUrl"
+ :disabled="formDisabled"
+ data-testid="alertRunbookField"
+ type="text"
+ />
</gl-form-group>
</div>
<template #modal-ok>
diff --git a/app/assets/javascripts/monitoring/services/alerts_service.js b/app/assets/javascripts/monitoring/services/alerts_service.js
index 4b7337972fe..a67675f1a3d 100644
--- a/app/assets/javascripts/monitoring/services/alerts_service.js
+++ b/app/assets/javascripts/monitoring/services/alerts_service.js
@@ -1,28 +1,39 @@
import axios from '~/lib/utils/axios_utils';
+const mapAlert = ({ runbook_url, ...alert }) => {
+ return { runbookUrl: runbook_url, ...alert };
+};
+
export default class AlertsService {
constructor({ alertsEndpoint }) {
this.alertsEndpoint = alertsEndpoint;
}
getAlerts() {
- return axios.get(this.alertsEndpoint).then(resp => resp.data);
+ return axios.get(this.alertsEndpoint).then(resp => mapAlert(resp.data));
}
- createAlert({ prometheus_metric_id, operator, threshold }) {
+ createAlert({ prometheus_metric_id, operator, threshold, runbookUrl }) {
return axios
- .post(this.alertsEndpoint, { prometheus_metric_id, operator, threshold })
- .then(resp => resp.data);
+ .post(this.alertsEndpoint, {
+ prometheus_metric_id,
+ operator,
+ threshold,
+ runbook_url: runbookUrl,
+ })
+ .then(resp => mapAlert(resp.data));
}
// eslint-disable-next-line class-methods-use-this
readAlert(alertPath) {
- return axios.get(alertPath).then(resp => resp.data);
+ return axios.get(alertPath).then(resp => mapAlert(resp.data));
}
// eslint-disable-next-line class-methods-use-this
- updateAlert(alertPath, { operator, threshold }) {
- return axios.put(alertPath, { operator, threshold }).then(resp => resp.data);
+ updateAlert(alertPath, { operator, threshold, runbookUrl }) {
+ return axios
+ .put(alertPath, { operator, threshold, runbook_url: runbookUrl })
+ .then(resp => mapAlert(resp.data));
}
// eslint-disable-next-line class-methods-use-this
diff --git a/app/assets/javascripts/monitoring/validators.js b/app/assets/javascripts/monitoring/validators.js
index cd426f1a221..c6b323f6360 100644
--- a/app/assets/javascripts/monitoring/validators.js
+++ b/app/assets/javascripts/monitoring/validators.js
@@ -1,3 +1,12 @@
+import { isSafeURL } from '~/lib/utils/url_utility';
+
+const isRunbookUrlValid = runbookUrl => {
+ if (!runbookUrl) {
+ return true;
+ }
+ return isSafeURL(runbookUrl);
+};
+
// Prop validator for alert information, expecting an object like the example below.
//
// {
@@ -8,6 +17,7 @@
// query: "rate(http_requests_total[5m])[30m:1m]",
// threshold: 0.002,
// title: "Core Usage (Total)",
+// runbookUrl: "https://www.gitlab.com/my-project/-/wikis/runbook"
// }
// }
export function alertsValidator(value) {
@@ -19,7 +29,8 @@ export function alertsValidator(value) {
alert.metricId &&
typeof alert.metricId === 'string' &&
alert.operator &&
- typeof alert.threshold === 'number'
+ typeof alert.threshold === 'number' &&
+ isRunbookUrlValid(alert.runbookUrl)
);
});
}
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index 4f61e5ed711..89ba2175b60 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -65,7 +65,7 @@ module IssuableCollections
def page_count_for_relation(relation, row_count)
limit = relation.limit_value.to_f
- return 1 if limit.zero?
+ return 1 if limit == 0
(row_count.to_f / limit).ceil
end
diff --git a/app/controllers/concerns/paginated_collection.rb b/app/controllers/concerns/paginated_collection.rb
index be84215a9e2..fcee4493314 100644
--- a/app/controllers/concerns/paginated_collection.rb
+++ b/app/controllers/concerns/paginated_collection.rb
@@ -6,7 +6,7 @@ module PaginatedCollection
private
def redirect_out_of_range(collection, total_pages = collection.total_pages)
- return false if total_pages.zero?
+ return false if total_pages == 0
out_of_range = collection.current_page > total_pages
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 23eb9901805..5076172985d 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -14,6 +14,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
push_frontend_feature_flag(:prometheus_computed_alerts)
push_frontend_feature_flag(:disable_metric_dashboard_refresh_rate)
+ push_frontend_feature_flag(:alert_runbooks)
end
before_action :authorize_read_environment!, except: [:metrics, :additional_metrics, :metrics_dashboard, :metrics_redirect]
before_action :authorize_create_environment!, only: [:new, :create]
diff --git a/app/graphql/resolvers/board_list_issues_resolver.rb b/app/graphql/resolvers/board_list_issues_resolver.rb
index 2c3308498f3..a7cc367379d 100644
--- a/app/graphql/resolvers/board_list_issues_resolver.rb
+++ b/app/graphql/resolvers/board_list_issues_resolver.rb
@@ -10,5 +10,10 @@ module Resolvers
service = Boards::Issues::ListService.new(list.board.resource_parent, context[:current_user], { board_id: list.board.id, id: list.id })
Gitlab::Graphql::Pagination::OffsetActiveRecordRelationConnection.new(service.execute)
end
+
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/235681
+ def self.complexity_multiplier(args)
+ 0.005
+ end
end
end
diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb
index 49b15cde009..24072d1ab46 100644
--- a/app/helpers/graph_helper.rb
+++ b/app/helpers/graph_helper.rb
@@ -17,7 +17,7 @@ module GraphHelper
end
def success_ratio(counts)
- return 100 if counts[:failed].zero?
+ return 100 if counts[:failed] == 0
ratio = (counts[:success].to_f / (counts[:success] + counts[:failed])) * 100
ratio.to_i
diff --git a/app/helpers/timeboxes_helper.rb b/app/helpers/timeboxes_helper.rb
index 8c437b283a0..34919f994ee 100644
--- a/app/helpers/timeboxes_helper.rb
+++ b/app/helpers/timeboxes_helper.rb
@@ -155,7 +155,7 @@ module TimeboxesHelper
opened = milestone.opened_issues_count
closed = milestone.closed_issues_count
- return _("Issues") if total.zero?
+ return _("Issues") if total == 0
content = []
@@ -187,7 +187,7 @@ module TimeboxesHelper
def milestone_releases_tooltip_text(milestone)
count = milestone.releases.count
- return _("Releases") if count.zero?
+ return _("Releases") if count == 0
n_("%{releases} release", "%{releases} releases", count) % { releases: count }
end
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
index 335d66cc7cc..e7cfa30a892 100644
--- a/app/models/audit_event.rb
+++ b/app/models/audit_event.rb
@@ -5,7 +5,7 @@ class AuditEvent < ApplicationRecord
include IgnorableColumns
include BulkInsertSafe
- PARALLEL_PERSISTENCE_COLUMNS = [:author_name, :entity_path, :target_details, :target_type].freeze
+ PARALLEL_PERSISTENCE_COLUMNS = [:author_name, :entity_path, :target_details].freeze
ignore_column :updated_at, remove_with: '13.4', remove_after: '2020-09-22'
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 30dd88ac72a..c153236dddd 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -409,7 +409,7 @@ module Ci
def legacy_stage(name)
stage = Ci::LegacyStage.new(self, name: name)
- stage unless stage.statuses_count.zero?
+ stage unless stage.statuses_count == 0
end
def ref_exists?
@@ -658,7 +658,7 @@ module Ci
end
def has_warnings?
- number_of_warnings.positive?
+ number_of_warnings > 0
end
def number_of_warnings
@@ -821,7 +821,7 @@ module Ci
return unless started_at
seconds = (started_at - created_at).to_i
- seconds unless seconds.zero?
+ seconds unless seconds == 0
end
def update_duration
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index 41215601704..cc9e32aa4c0 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -113,7 +113,7 @@ module Ci
end
def has_warnings?
- number_of_warnings.positive?
+ number_of_warnings > 0
end
def number_of_warnings
diff --git a/app/models/concerns/update_project_statistics.rb b/app/models/concerns/update_project_statistics.rb
index c0fa14d3369..a7028e18451 100644
--- a/app/models/concerns/update_project_statistics.rb
+++ b/app/models/concerns/update_project_statistics.rb
@@ -75,7 +75,7 @@ module UpdateProjectStatistics
end
def schedule_update_project_statistic(delta)
- return if delta.zero?
+ return if delta == 0
return if project.nil?
run_after_commit do
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 6341a798ecc..90893882fe5 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -631,7 +631,7 @@ class MergeRequestDiff < ApplicationRecord
def save_diffs
new_attributes = {}
- if compare.commits.size.zero?
+ if compare.commits.empty?
new_attributes[:state] = :empty
else
diff_collection = compare.diffs(Commit.max_diff_options)
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 58adfd5f70b..55326b9a282 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -127,7 +127,7 @@ class Milestone < ApplicationRecord
end
def can_be_closed?
- active? && issues.opened.count.zero?
+ active? && issues.opened.count == 0
end
def author_id
diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb
index 6b5ea0fc3fc..9da454125eb 100644
--- a/app/models/network/graph.rb
+++ b/app/models/network/graph.rb
@@ -211,7 +211,7 @@ module Network
# Visit branching chains
leaves.each do |l|
- parents = l.parents(@map).select {|p| p.space.zero?}
+ parents = l.parents(@map).select {|p| p.space == 0}
parents.each do |p|
place_chain(p, l.time)
end
@@ -266,14 +266,14 @@ module Network
def take_left_leaves(raw_commit)
commit = @map[raw_commit.id]
leaves = []
- leaves.push(commit) if commit.space.zero?
+ leaves.push(commit) if commit.space == 0
loop do
- return leaves if commit.parents(@map).count.zero?
+ return leaves if commit.parents(@map).count == 0
commit = commit.parents(@map).first
- return leaves unless commit.space.zero?
+ return leaves unless commit.space == 0
leaves.push(commit)
end
diff --git a/app/models/postgresql/replication_slot.rb b/app/models/postgresql/replication_slot.rb
index 7a123deb719..a4370eda5ba 100644
--- a/app/models/postgresql/replication_slot.rb
+++ b/app/models/postgresql/replication_slot.rb
@@ -33,7 +33,7 @@ module Postgresql
# If too many replicas are falling behind too much, the availability of a
# GitLab instance might suffer. To prevent this from happening we require
# at least 1 replica to have data recent enough.
- if sizes.any? && too_great.positive?
+ if sizes.any? && too_great > 0
(sizes.length - too_great) <= 1
else
false
diff --git a/app/models/project.rb b/app/models/project.rb
index fdca74af81d..87171b7debc 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1121,7 +1121,7 @@ class Project < ApplicationRecord
limit = creator.projects_limit
error =
- if limit.zero?
+ if limit == 0
_('Personal project creation is not allowed. Please contact your administrator with questions')
else
_('Your project limit is %{limit} projects! Please contact your administrator to increase it')
diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb
index c027a0d140e..8c72bd5ae7e 100644
--- a/app/models/suggestion.rb
+++ b/app/models/suggestion.rb
@@ -61,7 +61,7 @@ class Suggestion < ApplicationRecord
end
def single_line?
- lines_above.zero? && lines_below.zero?
+ lines_above == 0 && lines_below == 0
end
def target_line
diff --git a/app/models/user.rb b/app/models/user.rb
index 8c44494db56..2904dc9da76 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -962,7 +962,7 @@ class User < ApplicationRecord
def require_ssh_key?
count = Users::KeysCountService.new(self).count
- count.zero? && Gitlab::ProtocolAccess.allowed?('ssh')
+ count == 0 && Gitlab::ProtocolAccess.allowed?('ssh')
end
# rubocop: enable CodeReuse/ServiceClass
diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb
index 0855c67feb7..b7b9e7d1036 100644
--- a/app/serializers/merge_request_widget_entity.rb
+++ b/app/serializers/merge_request_widget_entity.rb
@@ -137,7 +137,7 @@ class MergeRequestWidgetEntity < Grape::Entity
merge_request.source_branch_exists? &&
merge_request.source_project&.uses_default_ci_config? &&
!merge_request.source_project.has_ci? &&
- merge_request.commits_count.positive? &&
+ merge_request.commits_count > 0 &&
can?(current_user, :read_build, merge_request.source_project) &&
can?(current_user, :create_pipeline, merge_request.source_project)
end
diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb
index 9e3c84d03ec..14e8683ebdf 100644
--- a/app/services/boards/issues/move_service.rb
+++ b/app/services/boards/issues/move_service.rb
@@ -130,7 +130,7 @@ module Boards
def move_between_ids(move_params)
ids = [move_params[:move_after_id], move_params[:move_before_id]]
.map(&:to_i)
- .map { |m| m.positive? ? m : nil }
+ .map { |m| m > 0 ? m : nil }
ids.any? ? ids : nil
end
diff --git a/app/services/cohorts_service.rb b/app/services/cohorts_service.rb
index 03be87f4cc1..7bc3b267a12 100644
--- a/app/services/cohorts_service.rb
+++ b/app/services/cohorts_service.rb
@@ -63,7 +63,7 @@ class CohortsService
overall_total = month_totals.first
month_totals.map do |total|
- { total: total, percentage: total.zero? ? 0 : 100 * total / overall_total }
+ { total: total, percentage: total == 0 ? 0 : 100 * total / overall_total }
end
end
diff --git a/app/services/discussions/resolve_service.rb b/app/services/discussions/resolve_service.rb
index 946fb5f1372..cd5925cd9be 100644
--- a/app/services/discussions/resolve_service.rb
+++ b/app/services/discussions/resolve_service.rb
@@ -56,7 +56,7 @@ module Discussions
def process_auto_merge
return unless merge_request
- return unless @resolved_count.positive?
+ return unless @resolved_count > 0
return unless discussions_ready_to_merge?
AutoMergeProcessWorker.perform_async(merge_request.id)
diff --git a/app/services/issues/reorder_service.rb b/app/services/issues/reorder_service.rb
index 02c18d31b5e..c82ad6ea501 100644
--- a/app/services/issues/reorder_service.rb
+++ b/app/services/issues/reorder_service.rb
@@ -40,7 +40,7 @@ module Issues
def move_between_ids
ids = [params[:move_after_id], params[:move_before_id]]
.map(&:to_i)
- .map { |m| m.positive? ? m : nil }
+ .map { |m| m > 0 ? m : nil }
ids.any? ? ids : nil
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 5a9f0b7fa68..9c60c54353f 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -72,7 +72,7 @@ module Notes
end
def do_commands(note, update_params, message, only_commands)
- return if quick_actions_service.commands_executed_count.to_i.zero?
+ return if quick_actions_service.commands_executed_count.to_i == 0
if update_params.present?
quick_actions_service.apply_updates(update_params, note)
diff --git a/app/services/packages/nuget/metadata_extraction_service.rb b/app/services/packages/nuget/metadata_extraction_service.rb
index 6fec398fab0..59125669f7d 100644
--- a/app/services/packages/nuget/metadata_extraction_service.rb
+++ b/app/services/packages/nuget/metadata_extraction_service.rb
@@ -42,7 +42,7 @@ module Packages
def valid_package_file?
package_file &&
package_file.package&.nuget? &&
- package_file.file.size.positive?
+ package_file.file.size > 0 # rubocop:disable Style/ZeroLengthPredicate
end
def extract_metadata(file)
diff --git a/app/services/packages/nuget/search_service.rb b/app/services/packages/nuget/search_service.rb
index f7e09e11819..b95aa30bec1 100644
--- a/app/services/packages/nuget/search_service.rb
+++ b/app/services/packages/nuget/search_service.rb
@@ -21,8 +21,8 @@ module Packages
@search_term = search_term
@options = DEFAULT_OPTIONS.merge(options)
- raise ArgumentError, 'negative per_page' if per_page.negative?
- raise ArgumentError, 'negative padding' if padding.negative?
+ raise ArgumentError, 'negative per_page' if per_page < 0
+ raise ArgumentError, 'negative padding' if padding < 0
end
def execute
diff --git a/app/services/projects/auto_devops/disable_service.rb b/app/services/projects/auto_devops/disable_service.rb
index c90510c581d..e10668ac9bd 100644
--- a/app/services/projects/auto_devops/disable_service.rb
+++ b/app/services/projects/auto_devops/disable_service.rb
@@ -23,7 +23,7 @@ module Projects
# for more context.
# rubocop: disable CodeReuse/ActiveRecord
def first_pipeline_failure?
- auto_devops_pipelines.success.limit(1).count.zero? &&
+ auto_devops_pipelines.success.limit(1).count == 0 &&
auto_devops_pipelines.failed.limit(1).count.nonzero?
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/services/projects/update_pages_service.rb b/app/services/projects/update_pages_service.rb
index 59389a0fa65..334f5993d15 100644
--- a/app/services/projects/update_pages_service.rb
+++ b/app/services/projects/update_pages_service.rb
@@ -136,7 +136,7 @@ module Projects
def max_size
max_pages_size = max_size_from_settings
- return ::Gitlab::Pages::MAX_SIZE if max_pages_size.zero?
+ return ::Gitlab::Pages::MAX_SIZE if max_pages_size == 0
max_pages_size
end
diff --git a/app/services/search_service.rb b/app/services/search_service.rb
index 650dc197f8c..278cf389e07 100644
--- a/app/services/search_service.rb
+++ b/app/services/search_service.rb
@@ -70,7 +70,7 @@ class SearchService
def per_page
per_page_param = params[:per_page].to_i
- return DEFAULT_PER_PAGE unless per_page_param.positive?
+ return DEFAULT_PER_PAGE unless per_page_param > 0
[MAX_PER_PAGE, per_page_param].min
end
diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb
index c84ac60d777..8d589c03259 100644
--- a/app/workers/admin_email_worker.rb
+++ b/app/workers/admin_email_worker.rb
@@ -18,7 +18,7 @@ class AdminEmailWorker # rubocop:disable Scalability/IdempotentWorker
# rubocop: disable CodeReuse/ActiveRecord
def send_repository_check_mail
repository_check_failed_count = Project.where(last_repository_check_failed: true).count
- return if repository_check_failed_count.zero?
+ return if repository_check_failed_count == 0
RepositoryCheckMailer.notify(repository_check_failed_count).deliver_now
end
diff --git a/app/workers/gitlab/import/advance_stage.rb b/app/workers/gitlab/import/advance_stage.rb
index 3f34437294e..9fc03efe9d0 100644
--- a/app/workers/gitlab/import/advance_stage.rb
+++ b/app/workers/gitlab/import/advance_stage.rb
@@ -41,7 +41,7 @@ module Gitlab
# complete the work fast enough.
waiter.wait(BLOCKING_WAIT_TIME)
- next unless waiter.jobs_remaining.positive?
+ next unless waiter.jobs_remaining > 0
new_waiters[waiter.key] = waiter.jobs_remaining
end
diff --git a/changelogs/unreleased/220322-refactor-target-type.yml b/changelogs/unreleased/220322-refactor-target-type.yml
deleted file mode 100644
index 559cd7f8f5c..00000000000
--- a/changelogs/unreleased/220322-refactor-target-type.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add new target_type column on audit_events table
-merge_request: 36198
-author:
-type: changed
diff --git a/changelogs/unreleased/224457-fix-data-integrity-on-issue_feedback-issue_links.yml b/changelogs/unreleased/224457-fix-data-integrity-on-issue_feedback-issue_links.yml
new file mode 100644
index 00000000000..0a9c0c80863
--- /dev/null
+++ b/changelogs/unreleased/224457-fix-data-integrity-on-issue_feedback-issue_links.yml
@@ -0,0 +1,5 @@
+---
+title: Add migration helper index for Vulnerabilities::Finding table
+merge_request: 38898
+author:
+type: changed
diff --git a/changelogs/unreleased/229548-adjust-telemetry-code-for-incidents.yml b/changelogs/unreleased/229548-adjust-telemetry-code-for-incidents.yml
new file mode 100644
index 00000000000..8de574de501
--- /dev/null
+++ b/changelogs/unreleased/229548-adjust-telemetry-code-for-incidents.yml
@@ -0,0 +1,5 @@
+---
+title: Update incident_issues usage ping to use issue type column
+merge_request: 38864
+author:
+type: changed
diff --git a/config/initializers/validate_puma.rb b/config/initializers/validate_puma.rb
index 5abcfbfe6be..ac5678c4b5a 100644
--- a/config/initializers/validate_puma.rb
+++ b/config/initializers/validate_puma.rb
@@ -1,5 +1,5 @@
# frozen_string_literal: true
-if Gitlab::Runtime.puma? && ::Puma.cli_config.options[:workers].to_i.zero?
+if Gitlab::Runtime.puma? && ::Puma.cli_config.options[:workers].to_i == 0
raise 'Puma is only supported in Cluster-mode: workers > 0'
end
diff --git a/db/post_migrate/20200807110237_add_migration_index_to_vulnerabilities_occurrences.rb b/db/post_migrate/20200807110237_add_migration_index_to_vulnerabilities_occurrences.rb
new file mode 100644
index 00000000000..e806a337f8f
--- /dev/null
+++ b/db/post_migrate/20200807110237_add_migration_index_to_vulnerabilities_occurrences.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+class AddMigrationIndexToVulnerabilitiesOccurrences < ActiveRecord::Migration[6.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :vulnerability_occurrences,
+ "project_id, report_type, encode(project_fingerprint, 'hex'::text)",
+ name: 'index_vulnerability_occurrences_for_issue_links_migration'
+ end
+
+ def down
+ remove_concurrent_index_by_name :vulnerability_occurrences,
+ :index_vulnerability_occurrences_for_issue_links_migration
+ end
+end
diff --git a/db/post_migrate/20200810100921_add_target_type_to_audit_event.rb b/db/post_migrate/20200810100921_add_target_type_to_audit_event.rb
deleted file mode 100644
index 8dde5945f0d..00000000000
--- a/db/post_migrate/20200810100921_add_target_type_to_audit_event.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-# frozen_string_literal: true
-
-class AddTargetTypeToAuditEvent < ActiveRecord::Migration[6.0]
- include Gitlab::Database::SchemaHelpers
- include Gitlab::Database::MigrationHelpers
-
- DOWNTIME = false
- SOURCE_TABLE_NAME = 'audit_events'
- PARTITIONED_TABLE_NAME = 'audit_events_part_5fc467ac26'
- TRIGGER_FUNCTION_NAME = 'table_sync_function_2be879775d'
-
- def up
- with_lock_retries do
- # rubocop:disable Migration/AddLimitToTextColumns
- add_column('audit_events', :target_type, :text)
- add_column('audit_events_part_5fc467ac26', :target_type, :text)
- # rubocop:enable Migration/AddLimitToTextColumns
-
- create_trigger_function(TRIGGER_FUNCTION_NAME, replace: true) do
- <<~SQL
- IF (TG_OP = 'DELETE') THEN
- DELETE FROM #{PARTITIONED_TABLE_NAME} where id = OLD.id;
- ELSIF (TG_OP = 'UPDATE') THEN
- UPDATE #{PARTITIONED_TABLE_NAME}
- SET author_id = NEW.author_id,
- type = NEW.type,
- entity_id = NEW.entity_id,
- entity_type = NEW.entity_type,
- details = NEW.details,
- ip_address = NEW.ip_address,
- author_name = NEW.author_name,
- entity_path = NEW.entity_path,
- target_details = NEW.target_details,
- target_type = NEW.target_type,
- created_at = NEW.created_at
- WHERE #{PARTITIONED_TABLE_NAME}.id = NEW.id;
- ELSIF (TG_OP = 'INSERT') THEN
- INSERT INTO #{PARTITIONED_TABLE_NAME} (id,
- author_id,
- type,
- entity_id,
- entity_type,
- details,
- ip_address,
- author_name,
- entity_path,
- target_details,
- target_type,
- created_at)
- VALUES (NEW.id,
- NEW.author_id,
- NEW.type,
- NEW.entity_id,
- NEW.entity_type,
- NEW.details,
- NEW.ip_address,
- NEW.author_name,
- NEW.entity_path,
- NEW.target_details,
- NEW.target_type,
- NEW.created_at);
- END IF;
- RETURN NULL;
- SQL
- end
- end
- end
-
- def down
- with_lock_retries do
- remove_column SOURCE_TABLE_NAME, :target_type
-
- create_trigger_function(TRIGGER_FUNCTION_NAME, replace: true) do
- <<~SQL
- IF (TG_OP = 'DELETE') THEN
- DELETE FROM #{PARTITIONED_TABLE_NAME} where id = OLD.id;
- ELSIF (TG_OP = 'UPDATE') THEN
- UPDATE #{PARTITIONED_TABLE_NAME}
- SET author_id = NEW.author_id,
- type = NEW.type,
- entity_id = NEW.entity_id,
- entity_type = NEW.entity_type,
- details = NEW.details,
- ip_address = NEW.ip_address,
- author_name = NEW.author_name,
- entity_path = NEW.entity_path,
- target_details = NEW.target_details,
- created_at = NEW.created_at
- WHERE #{PARTITIONED_TABLE_NAME}.id = NEW.id;
- ELSIF (TG_OP = 'INSERT') THEN
- INSERT INTO #{PARTITIONED_TABLE_NAME} (id,
- author_id,
- type,
- entity_id,
- entity_type,
- details,
- ip_address,
- author_name,
- entity_path,
- target_details,
- created_at)
- VALUES (NEW.id,
- NEW.author_id,
- NEW.type,
- NEW.entity_id,
- NEW.entity_type,
- NEW.details,
- NEW.ip_address,
- NEW.author_name,
- NEW.entity_path,
- NEW.target_details,
- NEW.created_at);
- END IF;
- RETURN NULL;
- SQL
- end
-
- remove_column PARTITIONED_TABLE_NAME, :target_type
- end
- end
-end
diff --git a/db/post_migrate/20200810101029_add_text_limit_to_audit_event_target_type.rb b/db/post_migrate/20200810101029_add_text_limit_to_audit_event_target_type.rb
deleted file mode 100644
index 2a5ea9cd245..00000000000
--- a/db/post_migrate/20200810101029_add_text_limit_to_audit_event_target_type.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-# frozen_string_literal: true
-
-class AddTextLimitToAuditEventTargetType < ActiveRecord::Migration[6.0]
- include Gitlab::Database::MigrationHelpers
- DOWNTIME = false
- SOURCE_TABLE_NAME = 'audit_events'
- PARTITIONED_TABLE_NAME = 'audit_events_part_5fc467ac26'
-
- disable_ddl_transaction!
-
- def up
- add_text_limit(SOURCE_TABLE_NAME, :target_type, 255)
- add_text_limit(PARTITIONED_TABLE_NAME, :target_type, 255)
- end
-
- def down
- remove_text_limit(SOURCE_TABLE_NAME, :target_type)
- remove_text_limit(PARTITIONED_TABLE_NAME, :target_type)
- end
-end
diff --git a/db/schema_migrations/20200807110237 b/db/schema_migrations/20200807110237
new file mode 100644
index 00000000000..fd02f50cdff
--- /dev/null
+++ b/db/schema_migrations/20200807110237
@@ -0,0 +1 @@
+5aeb0e74859df14e460b39b9f070624a02278c13b1da9393e1671b4c79635f52 \ No newline at end of file
diff --git a/db/schema_migrations/20200810100921 b/db/schema_migrations/20200810100921
deleted file mode 100644
index 14dc5a41721..00000000000
--- a/db/schema_migrations/20200810100921
+++ /dev/null
@@ -1 +0,0 @@
-fe96df46a5f360cafe8f9816c6dfc2d00afdcf458fb38ace37ce59999dba2413 \ No newline at end of file
diff --git a/db/schema_migrations/20200810101029 b/db/schema_migrations/20200810101029
deleted file mode 100644
index 59e0a236f3c..00000000000
--- a/db/schema_migrations/20200810101029
+++ /dev/null
@@ -1 +0,0 @@
-8bb03ea2ded957a41aa1efd60a9908a3c597aaaade9190f8f1515bfd2ab9a282 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 0a07834bb44..39479d54154 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -29,7 +29,6 @@ ELSIF (TG_OP = 'UPDATE') THEN
author_name = NEW.author_name,
entity_path = NEW.entity_path,
target_details = NEW.target_details,
- target_type = NEW.target_type,
created_at = NEW.created_at
WHERE audit_events_part_5fc467ac26.id = NEW.id;
ELSIF (TG_OP = 'INSERT') THEN
@@ -43,7 +42,6 @@ ELSIF (TG_OP = 'INSERT') THEN
author_name,
entity_path,
target_details,
- target_type,
created_at)
VALUES (NEW.id,
NEW.author_id,
@@ -55,7 +53,6 @@ ELSIF (TG_OP = 'INSERT') THEN
NEW.author_name,
NEW.entity_path,
NEW.target_details,
- NEW.target_type,
NEW.created_at);
END IF;
RETURN NULL;
@@ -77,10 +74,8 @@ CREATE TABLE public.audit_events_part_5fc467ac26 (
entity_path text,
target_details text,
created_at timestamp without time zone NOT NULL,
- target_type text,
CONSTRAINT check_492aaa021d CHECK ((char_length(entity_path) <= 5500)),
CONSTRAINT check_83ff8406e2 CHECK ((char_length(author_name) <= 255)),
- CONSTRAINT check_97a8c868e7 CHECK ((char_length(target_type) <= 255)),
CONSTRAINT check_d493ec90b5 CHECK ((char_length(target_details) <= 5500))
)
PARTITION BY RANGE (created_at);
@@ -9475,9 +9470,7 @@ CREATE TABLE public.audit_events (
author_name text,
entity_path text,
target_details text,
- target_type text,
CONSTRAINT check_492aaa021d CHECK ((char_length(entity_path) <= 5500)),
- CONSTRAINT check_82294106dd CHECK ((char_length(target_type) <= 255)),
CONSTRAINT check_83ff8406e2 CHECK ((char_length(author_name) <= 255)),
CONSTRAINT check_d493ec90b5 CHECK ((char_length(target_details) <= 5500))
);
@@ -20950,6 +20943,8 @@ CREATE UNIQUE INDEX index_vulnerability_occurrence_identifiers_on_unique_keys ON
CREATE INDEX index_vulnerability_occurrence_pipelines_on_pipeline_id ON public.vulnerability_occurrence_pipelines USING btree (pipeline_id);
+CREATE INDEX index_vulnerability_occurrences_for_issue_links_migration ON public.vulnerability_occurrences USING btree (project_id, report_type, encode(project_fingerprint, 'hex'::text));
+
CREATE INDEX index_vulnerability_occurrences_on_primary_identifier_id ON public.vulnerability_occurrences USING btree (primary_identifier_id);
CREATE INDEX index_vulnerability_occurrences_on_scanner_id ON public.vulnerability_occurrences USING btree (scanner_id);
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index e38e725fb97..ba59d467bc8 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -31,7 +31,14 @@ Example response:
"name": "user1",
"path": "user1",
"kind": "user",
- "full_path": "user1"
+ "full_path": "user1",
+ "parent_id": null,
+ "avatar_url": "https://secure.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon",
+ "web_url": "https://gitlab.example.com/user1",
+ "billable_members_count": 1,
+ "plan": "default",
+ "trial_ends_on": null,
+ "trial": false
},
{
"id": 2,
@@ -40,7 +47,13 @@ Example response:
"kind": "group",
"full_path": "group1",
"parent_id": null,
- "members_count_with_descendants": 2
+ "avatar_url": null,
+ "web_url": "https://gitlab.example.com/groups/group1",
+ "members_count_with_descendants": 2,
+ "billable_members_count": 2,
+ "plan": "default",
+ "trial_ends_on": null,
+ "trial": false
},
{
"id": 3,
@@ -49,7 +62,13 @@ Example response:
"kind": "group",
"full_path": "foo/bar",
"parent_id": 9,
- "members_count_with_descendants": 5
+ "avatar_url": null,
+ "web_url": "https://gitlab.example.com/groups/foo/bar",
+ "members_count_with_descendants": 5,
+ "billable_members_count": 5,
+ "plan": "default",
+ "trial_ends_on": null,
+ "trial": false
}
]
```
@@ -100,7 +119,13 @@ Example response:
"kind": "group",
"full_path": "twitter",
"parent_id": null,
- "members_count_with_descendants": 2
+ "avatar_url": null,
+ "web_url": "https://gitlab.example.com/groups/twitter",
+ "members_count_with_descendants": 2,
+ "billable_members_count": 2,
+ "plan": "default",
+ "trial_ends_on": null,
+ "trial": false
}
]
```
@@ -133,7 +158,13 @@ Example response:
"kind": "group",
"full_path": "group1",
"parent_id": null,
- "members_count_with_descendants": 2
+ "avatar_url": null,
+ "web_url": "https://gitlab.example.com/groups/group1",
+ "members_count_with_descendants": 2,
+ "billable_members_count": 2,
+ "plan": "default",
+ "trial_ends_on": null,
+ "trial": false
}
```
@@ -153,6 +184,12 @@ Example response:
"kind": "group",
"full_path": "group1",
"parent_id": null,
- "members_count_with_descendants": 2
+ "avatar_url": null,
+ "web_url": "https://gitlab.example.com/groups/group1",
+ "members_count_with_descendants": 2,
+ "billable_members_count": 2,
+ "plan": "default",
+ "trial_ends_on": null,
+ "trial": false
}
```
diff --git a/doc/operations/metrics/dashboards/img/metrics_dashboard_panel_preview_v13_3.png b/doc/operations/metrics/dashboards/img/metrics_dashboard_panel_preview_v13_3.png
new file mode 100644
index 00000000000..4f6d3b3dfa4
--- /dev/null
+++ b/doc/operations/metrics/dashboards/img/metrics_dashboard_panel_preview_v13_3.png
Binary files differ
diff --git a/doc/operations/metrics/dashboards/index.md b/doc/operations/metrics/dashboards/index.md
index b9008c18e13..d9d244ac7de 100644
--- a/doc/operations/metrics/dashboards/index.md
+++ b/doc/operations/metrics/dashboards/index.md
@@ -72,6 +72,51 @@ NOTE: **Note:**
Configuration files nested under subdirectories of `.gitlab/dashboards` are not
supported and won't be available in the UI.
+## Add a new metrics panel to a dashboard
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/228761) in GitLab 13.3 behind a disabled [feature flag](../../../administration/feature_flags.md): `metrics_dashboard_new_panel_page`.
+
+The metrics dashboard supports various [multiple panel types](../../../operations/metrics/dashboards/panel_types.md).
+You can quickly test how a panel configuration would display in your metrics dashboard
+with the **Add Panel** page:
+
+1. Sign in to GitLab as a user with Maintainer or Owner
+ [permissions](../../../user/permissions.md#project-members-permissions) on a
+ project that has the [feature flag enabled](#enable-or-disable-testing-metrics-panels).
+1. Open the URL `https://example.com/PROJECT/-/metrics/panel/new`, replacing
+ `example.com` with your domain name, and `PROJECT` with the name of your project,
+ to display the panel configuration page.
+1. In the **Define and preview panel** section, paste in the YAML you want to
+ preview in the **Panel YAML** field.
+1. Click **Preview panel**, and GitLab displays a preview of the chart below the
+ `Define and preview panel` section:
+ ![Monitoring Dashboard Add Panel page](img/metrics_dashboard_panel_preview_v13_3.png)
+
+### Enable or disable testing metrics panels
+
+Testing metrics panels in the UI is under development and not ready for production use. It's
+deployed behind a feature flag that's **disabled by default**.
+[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
+can enable it for your instance. Testing metrics panels in the UI can be enabled or disabled per-project.
+
+To enable it:
+
+```ruby
+# Instance-wide
+Feature.enable(:metrics_dashboard_new_panel_page)
+# or by project
+Feature.enable(:metrics_dashboard_new_panel_page, Project.find(metrics_dashboard_new_panel_page))
+```
+
+To disable it:
+
+```ruby
+# Instance-wide
+Feature.disable(:metrics_dashboard_new_panel_page)
+# or by project
+Feature.disable(:metrics_dashboard_new_panel_page, Project.find(metrics_dashboard_new_panel_page))
+```
+
## Duplicate a GitLab-defined dashboard
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/37238) in GitLab 12.7.
diff --git a/doc/user/analytics/img/vsa_filter_bar_v13.3.png b/doc/user/analytics/img/vsa_filter_bar_v13.3.png
new file mode 100644
index 00000000000..71e59892434
--- /dev/null
+++ b/doc/user/analytics/img/vsa_filter_bar_v13.3.png
Binary files differ
diff --git a/doc/user/analytics/value_stream_analytics.md b/doc/user/analytics/value_stream_analytics.md
index b11bae98af3..f78d381ea79 100644
--- a/doc/user/analytics/value_stream_analytics.md
+++ b/doc/user/analytics/value_stream_analytics.md
@@ -48,7 +48,30 @@ There are seven stages that are tracked as part of the Value Stream Analytics ca
- **Total** (Total)
- Total lifecycle time. That is, the velocity of the project or team. [Previously known](https://gitlab.com/gitlab-org/gitlab/-/issues/38317) as **Production**.
-## Date ranges
+## Filter the analytics data
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13216) in GitLab 13.3
+
+GitLab provides the ability to filter analytics based on the following parameters:
+
+- Milestones (Group level)
+- Labels (Group level)
+- Author
+- Assignees
+
+To filter results:
+
+1. Select a group.
+1. Click on the filter bar.
+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")
+
+NOTE: **Note:**
+Filtering is available only for group-level Value Stream Analytics.
+
+### Date ranges
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13216) in GitLab 12.4.
diff --git a/lib/gitlab/metrics/dashboard/validator/client.rb b/lib/gitlab/metrics/dashboard/validator/client.rb
index 6143edbd248..5d8ea448ac1 100644
--- a/lib/gitlab/metrics/dashboard/validator/client.rb
+++ b/lib/gitlab/metrics/dashboard/validator/client.rb
@@ -34,7 +34,7 @@ module Gitlab
end
def schemer
- @schemer ||= JSONSchemer.schema(Pathname.new(schema_path), formats: custom_formats.format_handlers)
+ @schemer ||= ::JSONSchemer.schema(Pathname.new(schema_path), formats: custom_formats.format_handlers)
end
def validate_against_schema
diff --git a/lib/gitlab/metrics/dashboard/validator/errors.rb b/lib/gitlab/metrics/dashboard/validator/errors.rb
index 104eb162209..0f6e687d291 100644
--- a/lib/gitlab/metrics/dashboard/validator/errors.rb
+++ b/lib/gitlab/metrics/dashboard/validator/errors.rb
@@ -9,19 +9,42 @@ module Gitlab
class SchemaValidationError < InvalidDashboardError
def initialize(error = {})
- if error.is_a?(Hash) && error.present?
- data = error["data"]
- data_pointer = error["data_pointer"]
- schema = error["schema"]
- schema_pointer = error["schema_pointer"]
+ super(error_message(error))
+ end
- msg = _("'%{data}' is invalid at '%{data_pointer}'. Should be '%{schema}' due to schema definition at '%{schema_pointer}'") %
- { data: data, data_pointer: data_pointer, schema: schema, schema_pointer: schema_pointer }
+ private
+
+ def error_message(error)
+ if error.is_a?(Hash) && error.present?
+ pretty(error)
else
- msg = "Dashboard failed schema validation"
+ "Dashboard failed schema validation"
end
+ end
+
+ # based on https://github.com/davishmcclurg/json_schemer/blob/master/lib/json_schemer/errors.rb
+ # with addition ability to translate error messages
+ def pretty(error)
+ data, data_pointer, type, schema = error.values_at('data', 'data_pointer', 'type', 'schema')
+ location = data_pointer.empty? ? 'root' : data_pointer
- super(msg)
+ case type
+ when 'required'
+ keys = error.fetch('details').fetch('missing_keys').join(', ')
+ _("%{location} is missing required keys: %{keys}") % { location: location, keys: keys }
+ when 'null', 'string', 'boolean', 'integer', 'number', 'array', 'object'
+ _("'%{data}' at %{location} is not of type: %{type}") % { data: data, location: location, type: type }
+ when 'pattern'
+ _("'%{data}' at %{location} does not match pattern: %{pattern}") % { data: data, location: location, pattern: schema.fetch('pattern') }
+ when 'format'
+ _("'%{data}' at %{location} does not match format: %{format}") % { data: data, location: location, format: schema.fetch('format') }
+ when 'const'
+ _("'%{data}' at %{location} is not: %{const}") % { data: data, location: location, const: schema.fetch('const').inspect }
+ when 'enum'
+ _("'%{data}' at %{location} is not one of: %{enum}") % { data: data, location: location, enum: schema.fetch('enum') }
+ else
+ _("'%{data}' at %{location} is invalid: error_type=%{type}") % { data: data, location: location, type: type }
+ end
end
end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 86bf43ea85d..e6a82da86b7 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -63,7 +63,6 @@ module Gitlab
# rubocop: disable Metrics/AbcSize
# rubocop: disable CodeReuse/ActiveRecord
def system_usage_data
- alert_bot_incident_count = count(::Issue.authored(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)
issues_created_manually_from_alerts = count(Issue.with_alert_management_alerts.not_authored_by(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id)
{
@@ -121,8 +120,8 @@ module Gitlab
issues_created_from_alerts: total_alert_issues,
issues_created_gitlab_alerts: issues_created_manually_from_alerts,
issues_created_manually_from_alerts: issues_created_manually_from_alerts,
- incident_issues: alert_bot_incident_count,
- alert_bot_incident_issues: alert_bot_incident_count,
+ incident_issues: count(::Issue.incident, start: issue_minimum_id, finish: issue_maximum_id),
+ alert_bot_incident_issues: count(::Issue.authored(::User.alert_bot), start: issue_minimum_id, finish: issue_maximum_id),
incident_labeled_issues: count(::Issue.with_label_attributes(::IncidentManagement::CreateIncidentLabelService::LABEL_PROPERTIES), start: issue_minimum_id, finish: issue_maximum_id),
keys: count(Key),
label_lists: count(List.label),
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index fe5821928bf..402e16d0954 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -527,6 +527,9 @@ msgstr ""
msgid "%{loadingIcon} Started"
msgstr ""
+msgid "%{location} is missing required keys: %{keys}"
+msgstr ""
+
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
@@ -806,7 +809,22 @@ msgstr ""
msgid "&lt;project name&gt;"
msgstr ""
-msgid "'%{data}' is invalid at '%{data_pointer}'. Should be '%{schema}' due to schema definition at '%{schema_pointer}'"
+msgid "'%{data}' at %{location} does not match format: %{format}"
+msgstr ""
+
+msgid "'%{data}' at %{location} does not match pattern: %{pattern}"
+msgstr ""
+
+msgid "'%{data}' at %{location} is invalid: error_type=%{type}"
+msgstr ""
+
+msgid "'%{data}' at %{location} is not of type: %{type}"
+msgstr ""
+
+msgid "'%{data}' at %{location} is not one of: %{enum}"
+msgstr ""
+
+msgid "'%{data}' at %{location} is not: %{const}"
msgstr ""
msgid "'%{level}' is not a valid visibility level"
@@ -7593,9 +7611,6 @@ msgstr ""
msgid "DastProfiles|Do you want to discard your changes?"
msgstr ""
-msgid "DastProfiles|Edit feature will come soon. Please create a new profile if changes needed"
-msgstr ""
-
msgid "DastProfiles|Edit site profile"
msgstr ""
@@ -10948,30 +10963,15 @@ msgstr ""
msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
msgstr ""
-msgid "GeoNodes|Attachments"
-msgstr ""
-
-msgid "GeoNodes|Checksummed"
-msgstr ""
-
msgid "GeoNodes|Consult Geo troubleshooting information"
msgstr ""
-msgid "GeoNodes|Container repositories"
-msgstr ""
-
msgid "GeoNodes|Data replication lag"
msgstr ""
-msgid "GeoNodes|Design repositories"
-msgstr ""
-
msgid "GeoNodes|Does not match the primary storage configuration"
msgstr ""
-msgid "GeoNodes|Failed"
-msgstr ""
-
msgid "GeoNodes|Full"
msgstr ""
@@ -10987,12 +10987,6 @@ msgstr ""
msgid "GeoNodes|Internal URL"
msgstr ""
-msgid "GeoNodes|Job artifacts"
-msgstr ""
-
-msgid "GeoNodes|LFS objects"
-msgstr ""
-
msgid "GeoNodes|Last event ID processed by cursor"
msgstr ""
@@ -11020,12 +11014,6 @@ msgstr ""
msgid "GeoNodes|Node's status was updated %{timeAgo}."
msgstr ""
-msgid "GeoNodes|Not checksummed"
-msgstr ""
-
-msgid "GeoNodes|Package files"
-msgstr ""
-
msgid "GeoNodes|Pausing replication stops the sync process. Are you sure?"
msgstr ""
@@ -11047,15 +11035,6 @@ msgstr ""
msgid "GeoNodes|Replication status"
msgstr ""
-msgid "GeoNodes|Repositories"
-msgstr ""
-
-msgid "GeoNodes|Repository checksum progress"
-msgstr ""
-
-msgid "GeoNodes|Repository verification progress"
-msgstr ""
-
msgid "GeoNodes|Selective (%{syncLabel})"
msgstr ""
@@ -11083,27 +11062,12 @@ msgstr ""
msgid "GeoNodes|Unused slots"
msgstr ""
-msgid "GeoNodes|Unverified"
-msgstr ""
-
msgid "GeoNodes|Updated %{timeAgo}"
msgstr ""
msgid "GeoNodes|Used slots"
msgstr ""
-msgid "GeoNodes|Verified"
-msgstr ""
-
-msgid "GeoNodes|Wiki checksum progress"
-msgstr ""
-
-msgid "GeoNodes|Wiki verification progress"
-msgstr ""
-
-msgid "GeoNodes|Wikis"
-msgstr ""
-
msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
msgstr ""
@@ -11116,6 +11080,12 @@ msgstr ""
msgid "GeoNodes|secondary nodes"
msgstr ""
+msgid "Geo|%{itemTitle} checksum progress"
+msgstr ""
+
+msgid "Geo|%{itemTitle} verification progress"
+msgstr ""
+
msgid "Geo|%{label} can't be blank"
msgstr ""
@@ -19414,7 +19384,7 @@ msgstr ""
msgid "PrometheusAlerts|Operator"
msgstr ""
-msgid "PrometheusAlerts|Runbook"
+msgid "PrometheusAlerts|Runbook URL (optional)"
msgstr ""
msgid "PrometheusAlerts|Select query"
diff --git a/spec/controllers/projects/ci/daily_build_group_report_results_controller_spec.rb b/spec/controllers/projects/ci/daily_build_group_report_results_controller_spec.rb
index 252ad6ec9c4..594c24bb7e3 100644
--- a/spec/controllers/projects/ci/daily_build_group_report_results_controller_spec.rb
+++ b/spec/controllers/projects/ci/daily_build_group_report_results_controller_spec.rb
@@ -149,8 +149,4 @@ RSpec.describe Projects::Ci::DailyBuildGroupReportResultsController do
date: date
)
end
-
- def csv_response
- CSV.parse(response.body)
- end
end
diff --git a/spec/factories/usage_data.rb b/spec/factories/usage_data.rb
index 76673095306..d2b8fd94aca 100644
--- a/spec/factories/usage_data.rb
+++ b/spec/factories/usage_data.rb
@@ -31,8 +31,8 @@ FactoryBot.define do
create(:project_error_tracking_setting, project: projects[1], enabled: false)
create(:alerts_service, project: projects[0])
create(:alerts_service, :inactive, project: projects[1])
- alert_bot_issues = create_list(:issue, 2, project: projects[0], author: User.alert_bot)
- create_list(:issue, 2, project: projects[1], author: User.alert_bot)
+ alert_bot_issues = create_list(:incident, 2, project: projects[0], author: User.alert_bot)
+ create_list(:incident, 2, project: projects[1], author: User.alert_bot)
issues = create_list(:issue, 4, project: projects[0])
create_list(:prometheus_alert, 2, project: projects[0])
create(:prometheus_alert, project: projects[1])
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_groups_missing_panels_and_group.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_groups_missing_panels_and_group.yml
new file mode 100644
index 00000000000..746a90f266e
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_groups_missing_panels_and_group.yml
@@ -0,0 +1,33 @@
+dashboard: 'Test Dashboard'
+panel_groups:
+- panels:
+ - title: "Super Chart A1"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 1
+ max_value: 1
+ metrics:
+ - id: metric_a1
+ query_range: |+
+ avg(
+ sum(
+ container_memory_usage_bytes{
+ container_name!="POD",
+ pod_name=~"^{{ci_environment_slug}}-(.*)",
+ namespace="{{kube_namespace}}"
+ }
+ ) by (job)
+ ) without (job)
+ /1024/1024/1024
+ unit: unit
+ label: Legend Label
+ - title: "Super Chart A2"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 2
+ metrics:
+ - id: metric_a2
+ query_range: 'query'
+ label: Legend Label
+ unit: unit
+- group: Group B
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_is_an_array.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_is_an_array.yml
new file mode 100644
index 00000000000..7627592553e
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_is_an_array.yml
@@ -0,0 +1,15 @@
+---
+- dashboard: 'Test Dashboard'
+ panel_groups:
+ - group: Group A
+ panels:
+ - title: "Super Chart A2"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 2
+ metrics:
+ - id: metric_a2
+ query_range: 'query'
+ label: Legend Label
+ unit: unit
+- dashboard: 'second entry'
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_missing_panel_groups.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_missing_panel_groups.yml
new file mode 100644
index 00000000000..6f9e22c3212
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_missing_panel_groups.yml
@@ -0,0 +1,32 @@
+dashboard: 'Test Dashboard'
+priority: 1
+links:
+ - title: Link 1
+ url: https://gitlab.com
+ - title: Link 2
+ url: https://docs.gitlab.com
+templating:
+ variables:
+ text_variable_full_syntax:
+ label: 'Variable 1'
+ type: text
+ options:
+ default_value: 'default'
+ text_variable_simple_syntax: 'default value'
+ custom_variable_simple_syntax: ['value1', 'value2', 'value3']
+ custom_variable_full_syntax:
+ label: 'Variable 2'
+ type: custom
+ options:
+ values:
+ - value: 'value option 1'
+ text: 'Option 1'
+ - value: 'value_option_2'
+ text: 'Option 2'
+ default: true
+ metric_label_values_variable:
+ label: 'Variable 3'
+ type: metric_label_values
+ options:
+ series_selector: 'backend:haproxy_backend_availability:ratio{env="{{env}}"}'
+ label: 'backend'
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_panel_is_missing_metrics.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_panel_is_missing_metrics.yml
new file mode 100644
index 00000000000..8f12365dca2
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_panel_is_missing_metrics.yml
@@ -0,0 +1,15 @@
+dashboard: 'Test Dashboard'
+panel_groups:
+- group: Group A
+ priority: 1
+ panels:
+ - title: "Super Chart A1"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 1
+ max_value: 1
+ - title: "Super Chart A2"
+ type: "area-chart"
+ y_label: "y_label"
+ weight: 2
+ metrics:
diff --git a/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_panle_groups_wrong_content_type.yml b/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_panle_groups_wrong_content_type.yml
new file mode 100644
index 00000000000..104107fa96e
--- /dev/null
+++ b/spec/fixtures/lib/gitlab/metrics/dashboard/dashboard_panle_groups_wrong_content_type.yml
@@ -0,0 +1,33 @@
+dashboard: 'Test Dashboard'
+priority: 1
+links:
+- title: Link 1
+ url: https://gitlab.com
+- title: Link 2
+ url: https://docs.gitlab.com
+templating:
+ variables:
+ text_variable_full_syntax:
+ label: 'Variable 1'
+ type: text
+ options:
+ default_value: 'default'
+ text_variable_simple_syntax: 'default value'
+ custom_variable_simple_syntax: ['value1', 'value2', 'value3']
+ custom_variable_full_syntax:
+ label: 'Variable 2'
+ type: custom
+ options:
+ values:
+ - value: 'value option 1'
+ text: 'Option 1'
+ - value: 'value_option_2'
+ text: 'Option 2'
+ default: true
+ metric_label_values_variable:
+ label: 'Variable 3'
+ type: metric_label_values
+ options:
+ series_selector: 'backend:haproxy_backend_availability:ratio{env="{{env}}"}'
+ label: 'backend'
+panel_groups: this should be an array
diff --git a/spec/frontend/monitoring/components/alert_widget_form_spec.js b/spec/frontend/monitoring/components/alert_widget_form_spec.js
index 1909c4c7cdb..799a19c3f58 100644
--- a/spec/frontend/monitoring/components/alert_widget_form_spec.js
+++ b/spec/frontend/monitoring/components/alert_widget_form_spec.js
@@ -1,5 +1,6 @@
import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui';
+import INVALID_URL from '~/lib/utils/invalid_url';
import AlertWidgetForm from '~/monitoring/components/alert_widget_form.vue';
import ModalStub from '../stubs/modal_stub';
@@ -24,7 +25,13 @@ describe('AlertWidgetForm', () => {
const propsWithAlertData = {
...defaultProps,
alertsToManage: {
- alert: { alert_path: alertPath, operator: '<', threshold: 5, metricId },
+ alert: {
+ alert_path: alertPath,
+ operator: '<',
+ threshold: 5,
+ metricId,
+ runbookUrl: INVALID_URL,
+ },
},
configuredAlert: metricId,
};
@@ -49,16 +56,11 @@ describe('AlertWidgetForm', () => {
const modal = () => wrapper.find(ModalStub);
const modalTitle = () => modal().attributes('title');
const submitButton = () => modal().find(GlLink);
- const alertRunbookField = () => wrapper.find('[data-testid="alertRunbookField"]');
+ const findRunbookField = () => modal().find('[data-testid="alertRunbookField"]');
+ const findThresholdField = () => modal().find('[data-qa-selector="alert_threshold_field"]');
const submitButtonTrackingOpts = () =>
JSON.parse(submitButton().attributes('data-tracking-options'));
- const e = {
- preventDefault: jest.fn(),
- };
-
- beforeEach(() => {
- e.preventDefault.mockReset();
- });
+ const stubEvent = { preventDefault: jest.fn() };
afterEach(() => {
if (wrapper) wrapper.destroy();
@@ -85,35 +87,34 @@ describe('AlertWidgetForm', () => {
expect(submitButtonTrackingOpts()).toEqual(dataTrackingOptions.create);
});
- it('emits a "create" event when form submitted without existing alert', () => {
- createComponent();
+ it('emits a "create" event when form submitted without existing alert', async () => {
+ createComponent(defaultProps, { alertRunbooks: true });
- wrapper.vm.selectQuery('9');
- wrapper.setData({
- threshold: 900,
- });
+ modal().vm.$emit('shown');
- wrapper.vm.handleSubmit(e);
+ findThresholdField().vm.$emit('input', 900);
+ findRunbookField().vm.$emit('input', INVALID_URL);
+
+ modal().vm.$emit('ok', stubEvent);
expect(wrapper.emitted().create[0]).toEqual([
{
alert: undefined,
operator: '>',
threshold: 900,
- prometheus_metric_id: '9',
+ prometheus_metric_id: '8',
+ runbookUrl: INVALID_URL,
},
]);
- expect(e.preventDefault).toHaveBeenCalledTimes(1);
});
it('resets form when modal is dismissed (hidden)', () => {
- createComponent();
+ createComponent(defaultProps, { alertRunbooks: true });
- wrapper.vm.selectQuery('9');
- wrapper.vm.selectQuery('>');
- wrapper.setData({
- threshold: 800,
- });
+ modal().vm.$emit('shown');
+
+ findThresholdField().vm.$emit('input', 800);
+ findRunbookField().vm.$emit('input', INVALID_URL);
modal().vm.$emit('hidden');
@@ -121,6 +122,7 @@ describe('AlertWidgetForm', () => {
expect(wrapper.vm.operator).toBe(null);
expect(wrapper.vm.threshold).toBe(null);
expect(wrapper.vm.prometheusMetricId).toBe(null);
+ expect(wrapper.vm.runbookUrl).toBe(null);
});
it('sets selectedAlert to the provided configuredAlert on modal show', () => {
@@ -167,7 +169,7 @@ describe('AlertWidgetForm', () => {
beforeEach(() => {
createComponent(propsWithAlertData);
- wrapper.vm.selectQuery(metricId);
+ modal().vm.$emit('shown');
});
it('sets tracking options for delete alert', () => {
@@ -180,7 +182,7 @@ describe('AlertWidgetForm', () => {
});
it('emits "delete" event when form values unchanged', () => {
- wrapper.vm.handleSubmit(e);
+ modal().vm.$emit('ok', stubEvent);
expect(wrapper.emitted().delete[0]).toEqual([
{
@@ -188,51 +190,58 @@ describe('AlertWidgetForm', () => {
operator: '<',
threshold: 5,
prometheus_metric_id: '8',
+ runbookUrl: INVALID_URL,
},
]);
- expect(e.preventDefault).toHaveBeenCalledTimes(1);
});
+ });
- it('emits "update" event when form changed', () => {
- wrapper.setData({
- threshold: 11,
- });
+ it('emits "update" event when form changed', () => {
+ const updatedRunbookUrl = `${INVALID_URL}/test`;
- wrapper.vm.handleSubmit(e);
+ createComponent(propsWithAlertData, { alertRunbooks: true });
- expect(wrapper.emitted().update[0]).toEqual([
- {
- alert: 'alert',
- operator: '<',
- threshold: 11,
- prometheus_metric_id: '8',
- },
- ]);
- expect(e.preventDefault).toHaveBeenCalledTimes(1);
- });
+ modal().vm.$emit('shown');
+
+ findRunbookField().vm.$emit('input', updatedRunbookUrl);
+ findThresholdField().vm.$emit('input', 11);
- it('sets tracking options for update alert', () => {
- wrapper.setData({
+ modal().vm.$emit('ok', stubEvent);
+
+ expect(wrapper.emitted().update[0]).toEqual([
+ {
+ alert: 'alert',
+ operator: '<',
threshold: 11,
- });
+ prometheus_metric_id: '8',
+ runbookUrl: updatedRunbookUrl,
+ },
+ ]);
+ });
- return wrapper.vm.$nextTick(() => {
- expect(submitButtonTrackingOpts()).toEqual(dataTrackingOptions.update);
- });
- });
+ it('sets tracking options for update alert', async () => {
+ createComponent(propsWithAlertData);
+
+ modal().vm.$emit('shown');
+
+ findThresholdField().vm.$emit('input', 11);
+
+ await wrapper.vm.$nextTick();
+
+ expect(submitButtonTrackingOpts()).toEqual(dataTrackingOptions.update);
});
describe('alert runbooks feature flag', () => {
it('hides the runbook field when the flag is disabled', () => {
createComponent(undefined, { alertRunbooks: false });
- expect(alertRunbookField().exists()).toBe(false);
+ expect(findRunbookField().exists()).toBe(false);
});
it('shows the runbook field when the flag is enabled', () => {
createComponent(undefined, { alertRunbooks: true });
- expect(alertRunbookField().exists()).toBe(true);
+ expect(findRunbookField().exists()).toBe(true);
});
});
});
diff --git a/spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb b/spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb
index bf1629e33dd..f0db1bd0d33 100644
--- a/spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/validator/errors_spec.rb
@@ -4,28 +4,130 @@ require 'spec_helper'
RSpec.describe Gitlab::Metrics::Dashboard::Validator::Errors do
describe Gitlab::Metrics::Dashboard::Validator::Errors::SchemaValidationError do
- context 'valid error hash from jsonschemer' do
+ context 'empty error hash' do
+ let(:error_hash) { {} }
+
+ it 'uses default error message' do
+ expect(described_class.new(error_hash).message).to eq('Dashboard failed schema validation')
+ end
+ end
+
+ context 'formatted message' do
+ subject { described_class.new(error_hash).message }
+
let(:error_hash) do
{
- 'data' => 'data',
- 'data_pointer' => 'data_pointer',
- 'schema' => 'schema',
- 'schema_pointer' => 'schema_pointer'
+ 'data' => 'property_name',
+ 'data_pointer' => pointer,
+ 'type' => type,
+ 'schema' => 'schema',
+ 'details' => details
}
end
- it 'formats message' do
- expect(described_class.new(error_hash).message).to eq(
- "'data' is invalid at 'data_pointer'. Should be 'schema' due to schema definition at 'schema_pointer'"
- )
+ context 'for root object' do
+ let(:pointer) { '' }
+
+ context 'when required keys are missing' do
+ let(:type) { 'required' }
+ let(:details) { { 'missing_keys' => ['one'] } }
+
+ it { is_expected.to eq 'root is missing required keys: one' }
+ end
end
- end
- context 'empty error hash' do
- let(:error_hash) { {} }
+ context 'for nested object' do
+ let(:pointer) { '/nested_objects/0' }
- it 'uses default error message' do
- expect(described_class.new(error_hash).message).to eq('Dashboard failed schema validation')
+ context 'when required keys are missing' do
+ let(:type) { 'required' }
+ let(:details) { { 'missing_keys' => ['two'] } }
+
+ it { is_expected.to eq '/nested_objects/0 is missing required keys: two' }
+ end
+
+ context 'when there is type mismatch' do
+ %w(null string boolean integer number array object).each do |expected_type|
+ context "on type: #{expected_type}" do
+ let(:type) { expected_type }
+ let(:details) { nil }
+
+ subject { described_class.new(error_hash).message }
+
+ it { is_expected.to eq "'property_name' at /nested_objects/0 is not of type: #{expected_type}" }
+ end
+ end
+ end
+
+ context 'when data does not match pattern' do
+ let(:type) { 'pattern' }
+ let(:error_hash) do
+ {
+ 'data' => 'property_name',
+ 'data_pointer' => pointer,
+ 'type' => type,
+ 'schema' => { 'pattern' => 'aa.*' }
+ }
+ end
+
+ it { is_expected.to eq "'property_name' at /nested_objects/0 does not match pattern: aa.*" }
+ end
+
+ context 'when data does not match format' do
+ let(:type) { 'format' }
+ let(:error_hash) do
+ {
+ 'data' => 'property_name',
+ 'data_pointer' => pointer,
+ 'type' => type,
+ 'schema' => { 'format' => 'date-time' }
+ }
+ end
+
+ it { is_expected.to eq "'property_name' at /nested_objects/0 does not match format: date-time" }
+ end
+
+ context 'when data is not const' do
+ let(:type) { 'const' }
+ let(:error_hash) do
+ {
+ 'data' => 'property_name',
+ 'data_pointer' => pointer,
+ 'type' => type,
+ 'schema' => { 'const' => 'one' }
+ }
+ end
+
+ it { is_expected.to eq "'property_name' at /nested_objects/0 is not: \"one\"" }
+ end
+
+ context 'when data is not included in enum' do
+ let(:type) { 'enum' }
+ let(:error_hash) do
+ {
+ 'data' => 'property_name',
+ 'data_pointer' => pointer,
+ 'type' => type,
+ 'schema' => { 'enum' => %w(one two) }
+ }
+ end
+
+ it { is_expected.to eq "'property_name' at /nested_objects/0 is not one of: [\"one\", \"two\"]" }
+ end
+
+ context 'when data is not included in enum' do
+ let(:type) { 'unknown' }
+ let(:error_hash) do
+ {
+ 'data' => 'property_name',
+ 'data_pointer' => pointer,
+ 'type' => type,
+ 'schema' => 'schema'
+ }
+ end
+
+ it { is_expected.to eq "'property_name' at /nested_objects/0 is invalid: error_type=unknown" }
+ end
end
end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/validator_spec.rb b/spec/lib/gitlab/metrics/dashboard/validator_spec.rb
index 22c6ec310e2..338d6fdcd89 100644
--- a/spec/lib/gitlab/metrics/dashboard/validator_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/validator_spec.rb
@@ -34,6 +34,15 @@ RSpec.describe Gitlab::Metrics::Dashboard::Validator do
end
describe '#validate!' do
+ shared_examples 'validation failed' do |errors_message|
+ it 'raises error with corresponding messages', :aggregate_failures do
+ expect { subject }.to raise_error do |error|
+ expect(error).to be_kind_of(Gitlab::Metrics::Dashboard::Validator::Errors::InvalidDashboardError)
+ expect(error.message).to eq(errors_message)
+ end
+ end
+ end
+
context 'valid dashboard' do
it 'returns true' do
expect(described_class.validate!(valid_dashboard)).to be true
@@ -41,22 +50,37 @@ RSpec.describe Gitlab::Metrics::Dashboard::Validator do
end
context 'invalid dashboard' do
+ subject { described_class.validate!(invalid_dashboard) }
+
context 'invalid schema' do
- it 'raises error' do
- expect { described_class.validate!(invalid_dashboard) }
- .to raise_error(Gitlab::Metrics::Dashboard::Validator::Errors::InvalidDashboardError,
- "'this_should_be_a_int' is invalid at '/panel_groups/0/panels/0/weight'."\
- " Should be '{\"type\"=>\"number\"}' due to schema definition at '/properties/weight'")
+ context 'wrong property type' do
+ it_behaves_like 'validation failed', "'this_should_be_a_int' at /panel_groups/0/panels/0/weight is not of type: number"
+ end
+
+ context 'panel groups missing' do
+ let_it_be(:invalid_dashboard) { load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/dashboard_missing_panel_groups.yml')) }
+
+ it_behaves_like 'validation failed', 'root is missing required keys: panel_groups'
+ end
+
+ context 'groups are missing panels and group keys' do
+ let_it_be(:invalid_dashboard) { load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/dashboard_groups_missing_panels_and_group.yml')) }
+
+ it_behaves_like 'validation failed', '/panel_groups/0 is missing required keys: group'
+ end
+
+ context 'panel is missing metrics key' do
+ let_it_be(:invalid_dashboard) { load_dashboard_yaml(fixture_file('lib/gitlab/metrics/dashboard/dashboard_panel_is_missing_metrics.yml')) }
+
+ it_behaves_like 'validation failed', '/panel_groups/0/panels/0 is missing required keys: metrics'
end
end
context 'duplicate metric ids' do
context 'with no project given' do
- it 'checks against given dashboard and returns false' do
- expect { described_class.validate!(duplicate_id_dashboard) }
- .to raise_error(Gitlab::Metrics::Dashboard::Validator::Errors::InvalidDashboardError,
- "metric_id must be unique across a project")
- end
+ subject { described_class.validate!(duplicate_id_dashboard) }
+
+ it_behaves_like 'validation failed', 'metric_id must be unique across a project'
end
end
end