summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml4
-rw-r--r--.rubocop.yml3
-rw-r--r--GITLAB_ELASTICSEARCH_INDEXER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/error_tracking/components/error_details.vue70
-rw-r--r--app/assets/javascripts/error_tracking/details.js4
-rw-r--r--app/assets/javascripts/error_tracking/services/index.js3
-rw-r--r--app/assets/javascripts/error_tracking/store/actions.js19
-rw-r--r--app/assets/javascripts/error_tracking/store/details/state.js2
-rw-r--r--app/assets/javascripts/error_tracking/store/index.js7
-rw-r--r--app/assets/javascripts/error_tracking/store/mutation_types.js2
-rw-r--r--app/assets/javascripts/error_tracking/store/mutations.js10
-rw-r--r--app/assets/stylesheets/pages/error_details.scss5
-rw-r--r--app/controllers/projects/environments_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb9
-rw-r--r--app/helpers/projects/error_tracking_helper.rb1
-rw-r--r--app/models/concerns/protected_ref.rb2
-rw-r--r--app/models/diff_viewer/base.rb3
-rw-r--r--changelogs/unreleased/118662-drop-support-es-v5-support-v7.yml5
-rw-r--r--changelogs/unreleased/14857-activate-promethus-integration-for-projects.yml5
-rw-r--r--changelogs/unreleased/39825-update-sentry-error-status-FE.yml5
-rw-r--r--changelogs/unreleased/add-warnings-to-sidekiq-rake-tasks.yml5
-rw-r--r--changelogs/unreleased/include-subgroups-in-group-search.yml5
-rw-r--r--changelogs/unreleased/return_slug_in_api_services_index.yml5
-rw-r--r--changelogs/unreleased/sample-metrics-from-ui.yml5
-rw-r--r--config/initializers/elastic_client_setup.rb26
-rw-r--r--config/routes/project.rb2
-rw-r--r--db/fixtures/development/07_milestones.rb3
-rw-r--r--db/migrate/20200109085206_create_approval_project_rules_protected_branches.rb19
-rw-r--r--db/post_migrate/20200114112932_add_temporary_partial_index_on_project_id_to_services.rb22
-rw-r--r--db/post_migrate/20200114113341_patch_prometheus_services_for_shared_cluster_applications.rb88
-rw-r--r--db/schema.rb12
-rw-r--r--doc/api/packages.md30
-rw-r--r--doc/api/services.md3
-rw-r--r--doc/ci/cloud_deployment/index.md19
-rw-r--r--doc/development/gotchas.md8
-rw-r--r--doc/integration/elasticsearch.md5
-rw-r--r--doc/user/markdown.md8
-rw-r--r--lib/api/entities.rb7
-rw-r--r--lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications.rb52
-rw-r--r--lib/gitlab/git/gitmodules_parser.rb2
-rw-r--r--lib/gitlab/group_search_results.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb2
-rw-r--r--lib/gitlab/sidekiq_config.rb85
-rw-r--r--lib/gitlab/sidekiq_config/cli_methods.rb43
-rw-r--r--lib/tasks/sidekiq.rake25
-rw-r--r--locale/gitlab.pot9
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb5
-rw-r--r--rubocop/cop/rspec/have_gitlab_http_status.rb119
-rw-r--r--rubocop/rubocop.rb1
-rw-r--r--spec/controllers/projects/environments/sample_metrics_controller_spec.rb11
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/service.json1
-rw-r--r--spec/frontend/error_tracking/components/error_details_spec.js5
-rw-r--r--spec/frontend/error_tracking/store/actions_spec.js78
-rw-r--r--spec/frontend/error_tracking/store/details/actions_spec.js2
-rw-r--r--spec/helpers/projects/error_tracking_helper_spec.rb5
-rw-r--r--spec/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications_spec.rb75
-rw-r--r--spec/lib/gitlab/group_search_results_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/dashboard/processor_spec.rb18
-rw-r--r--spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb22
-rw-r--r--spec/migrations/patch_prometheus_services_for_shared_cluster_applications_spec.rb134
-rw-r--r--spec/models/diff_viewer/base_spec.rb28
-rw-r--r--spec/requests/api/services_spec.rb2
-rw-r--r--spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb102
-rw-r--r--spec/support/cycle_analytics_helpers/test_generation.rb2
-rw-r--r--spec/support/migrations_helpers/prometheus_service_helpers.rb35
67 files changed, 1158 insertions, 154 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 0673cce0e2e..4c407045411 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -213,7 +213,7 @@
- name: postgres:9.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
- - name: elasticsearch:5.6.12
+ - name: elasticsearch:6.4.2
.use-pg10-ee:
image: "registry.gitlab.com/gitlab-org/gitlab-build-images:ruby-2.6.5-golang-1.12-git-2.24-lfs-2.9-chrome-73.0-node-12.x-yarn-1.16-postgresql-10-graphicsmagick-1.3.33"
@@ -221,7 +221,7 @@
- name: postgres:10.9
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine
- - name: elasticsearch:5.6.12
+ - name: elasticsearch:6.4.2
.only-ee:
only:
diff --git a/.rubocop.yml b/.rubocop.yml
index b65f9e8aff1..da14413deb7 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -361,6 +361,9 @@ RSpec/MissingExampleGroupArgument:
RSpec/UnspecifiedException:
Enabled: false
+RSpec/HaveGitlabHttpStatus:
+ Enabled: false
+
Style/MultilineWhenThen:
Enabled: false
diff --git a/GITLAB_ELASTICSEARCH_INDEXER_VERSION b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
index bc80560fad6..227cea21564 100644
--- a/GITLAB_ELASTICSEARCH_INDEXER_VERSION
+++ b/GITLAB_ELASTICSEARCH_INDEXER_VERSION
@@ -1 +1 @@
-1.5.0
+2.0.0
diff --git a/Gemfile b/Gemfile
index dc1e3762a8b..5e8d0dd3c0e 100644
--- a/Gemfile
+++ b/Gemfile
@@ -19,7 +19,7 @@ gem 'default_value_for', '~> 3.3.0'
gem 'pg', '~> 1.1'
gem 'rugged', '~> 0.28'
-gem 'grape-path-helpers', '~> 1.1'
+gem 'grape-path-helpers', '~> 1.2'
gem 'faraday', '~> 0.12'
gem 'marginalia', '~> 1.8.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 2c24085a34c..5ff25c1eeb0 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -432,7 +432,7 @@ GEM
grape-entity (0.7.1)
activesupport (>= 4.0)
multi_json (>= 1.3.2)
- grape-path-helpers (1.1.0)
+ grape-path-helpers (1.2.0)
activesupport
grape (~> 1.0)
rake (~> 12)
@@ -1230,7 +1230,7 @@ DEPENDENCIES
gpgme (~> 2.0.19)
grape (~> 1.1.0)
grape-entity (~> 0.7.1)
- grape-path-helpers (~> 1.1)
+ grape-path-helpers (~> 1.2)
grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10)
graphql (~> 1.9.11)
diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue
index 23a1ec4e367..43e18486ae9 100644
--- a/app/assets/javascripts/error_tracking/components/error_details.vue
+++ b/app/assets/javascripts/error_tracking/components/error_details.vue
@@ -30,6 +30,14 @@ export default {
},
mixins: [timeagoMixin],
props: {
+ listPath: {
+ type: String,
+ required: true,
+ },
+ issueUpdatePath: {
+ type: String,
+ required: true,
+ },
issueId: {
type: String,
required: true,
@@ -81,7 +89,14 @@ export default {
};
},
computed: {
- ...mapState('details', ['error', 'loading', 'loadingStacktrace', 'stacktraceData']),
+ ...mapState('details', [
+ 'error',
+ 'loading',
+ 'loadingStacktrace',
+ 'stacktraceData',
+ 'updatingResolveStatus',
+ 'updatingIgnoreStatus',
+ ]),
...mapGetters('details', ['stacktrace']),
reported() {
return sprintf(
@@ -137,12 +152,15 @@ export default {
this.startPollingStacktrace(this.issueStackTracePath);
},
methods: {
- ...mapActions('details', ['startPollingDetails', 'startPollingStacktrace']),
+ ...mapActions('details', ['startPollingDetails', 'startPollingStacktrace', 'updateStatus']),
trackClickErrorLinkToSentryOptions,
createIssue() {
this.issueCreationInProgress = true;
this.$refs.sentryIssueForm.submit();
},
+ updateIssueStatus(status) {
+ this.updateStatus({ endpoint: this.issueUpdatePath, redirectUrl: this.listPath, status });
+ },
formatDate(date) {
return `${this.timeFormatted(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`;
},
@@ -158,24 +176,42 @@ export default {
<div v-else-if="showDetails" class="error-details">
<div class="top-area align-items-center justify-content-between py-3">
<span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span>
- <form ref="sentryIssueForm" :action="projectIssuesPath" method="POST">
- <gl-form-input class="hidden" name="issue[title]" :value="issueTitle" />
- <input name="issue[description]" :value="issueDescription" type="hidden" />
- <gl-form-input
- :value="GQLerror.id"
- class="hidden"
- name="issue[sentry_issue_attributes][sentry_issue_identifier]"
+ <div class="d-inline-flex">
+ <loading-button
+ :label="__('Ignore')"
+ :loading="updatingIgnoreStatus"
+ @click="updateIssueStatus('ignored')"
/>
- <gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" />
<loading-button
- v-if="!error.gitlab_issue"
- class="btn-success"
- :label="__('Create issue')"
- :loading="issueCreationInProgress"
- data-qa-selector="create_issue_button"
- @click="createIssue"
+ class="btn-outline-info ml-2"
+ :label="__('Resolve')"
+ :loading="updatingResolveStatus"
+ @click="updateIssueStatus('resolved')"
/>
- </form>
+ <form
+ ref="sentryIssueForm"
+ :action="projectIssuesPath"
+ method="POST"
+ class="d-inline-block ml-2"
+ >
+ <gl-form-input class="hidden" name="issue[title]" :value="issueTitle" />
+ <input name="issue[description]" :value="issueDescription" type="hidden" />
+ <gl-form-input
+ :value="GQLerror.id"
+ class="hidden"
+ name="issue[sentry_issue_attributes][sentry_issue_identifier]"
+ />
+ <gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" />
+ <loading-button
+ v-if="!error.gitlab_issue"
+ class="btn-success"
+ :label="__('Create issue')"
+ :loading="issueCreationInProgress"
+ data-qa-selector="create_issue_button"
+ @click="createIssue"
+ />
+ </form>
+ </div>
</div>
<div>
<tooltip-on-truncate :title="GQLerror.title" truncate-target="child" placement="top">
diff --git a/app/assets/javascripts/error_tracking/details.js b/app/assets/javascripts/error_tracking/details.js
index b9761cdf2c1..c18298dec4f 100644
--- a/app/assets/javascripts/error_tracking/details.js
+++ b/app/assets/javascripts/error_tracking/details.js
@@ -25,6 +25,8 @@ export default () => {
const {
issueId,
projectPath,
+ listPath,
+ issueUpdatePath,
issueDetailsPath,
issueStackTracePath,
projectIssuesPath,
@@ -34,6 +36,8 @@ export default () => {
props: {
issueId,
projectPath,
+ listPath,
+ issueUpdatePath,
issueDetailsPath,
issueStackTracePath,
projectIssuesPath,
diff --git a/app/assets/javascripts/error_tracking/services/index.js b/app/assets/javascripts/error_tracking/services/index.js
index 3b3f8311d67..3fb317c17f5 100644
--- a/app/assets/javascripts/error_tracking/services/index.js
+++ b/app/assets/javascripts/error_tracking/services/index.js
@@ -4,4 +4,7 @@ export default {
getSentryData({ endpoint, params }) {
return axios.get(endpoint, { params });
},
+ updateErrorStatus(endpoint, status) {
+ return axios.put(endpoint, { status });
+ },
};
diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js
new file mode 100644
index 00000000000..bb8b039b5df
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/actions.js
@@ -0,0 +1,19 @@
+import service from './../services';
+import * as types from './mutation_types';
+import createFlash from '~/flash';
+import { visitUrl } from '~/lib/utils/url_utility';
+import { __ } from '~/locale';
+
+export function updateStatus({ commit }, { endpoint, redirectUrl, status }) {
+ const type =
+ status === 'resolved' ? types.SET_UPDATING_RESOLVE_STATUS : types.SET_UPDATING_IGNORE_STATUS;
+ commit(type, true);
+
+ return service
+ .updateErrorStatus(endpoint, status)
+ .then(() => visitUrl(redirectUrl))
+ .catch(() => createFlash(__('Failed to update issue status')))
+ .finally(() => commit(type, false));
+}
+
+export default () => {};
diff --git a/app/assets/javascripts/error_tracking/store/details/state.js b/app/assets/javascripts/error_tracking/store/details/state.js
index 95fb0ba0558..52b0297607d 100644
--- a/app/assets/javascripts/error_tracking/store/details/state.js
+++ b/app/assets/javascripts/error_tracking/store/details/state.js
@@ -3,4 +3,6 @@ export default () => ({
stacktraceData: {},
loading: true,
loadingStacktrace: true,
+ updatingResolveStatus: false,
+ updatingIgnoreStatus: false,
});
diff --git a/app/assets/javascripts/error_tracking/store/index.js b/app/assets/javascripts/error_tracking/store/index.js
index ad05eecef6c..75aa78d9c07 100644
--- a/app/assets/javascripts/error_tracking/store/index.js
+++ b/app/assets/javascripts/error_tracking/store/index.js
@@ -1,6 +1,9 @@
import Vue from 'vue';
import Vuex from 'vuex';
+import * as actions from './actions';
+import mutations from './mutations';
+
import * as listActions from './list/actions';
import listMutations from './list/mutations';
import listState from './list/state';
@@ -24,8 +27,8 @@ export const createStore = () =>
details: {
namespaced: true,
state: detailsState(),
- actions: detailsActions,
- mutations: detailsMutations,
+ actions: { ...actions, ...detailsActions },
+ mutations: { ...mutations, ...detailsMutations },
getters: detailsGetters,
},
},
diff --git a/app/assets/javascripts/error_tracking/store/mutation_types.js b/app/assets/javascripts/error_tracking/store/mutation_types.js
new file mode 100644
index 00000000000..30aebacbedd
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/mutation_types.js
@@ -0,0 +1,2 @@
+export const SET_UPDATING_RESOLVE_STATUS = 'SET_UPDATING_RESOLVE_STATUS';
+export const SET_UPDATING_IGNORE_STATUS = 'SET_UPDATING_IGNORE_STATUS';
diff --git a/app/assets/javascripts/error_tracking/store/mutations.js b/app/assets/javascripts/error_tracking/store/mutations.js
new file mode 100644
index 00000000000..c7a7e46df40
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/mutations.js
@@ -0,0 +1,10 @@
+import * as types from './mutation_types';
+
+export default {
+ [types.SET_UPDATING_IGNORE_STATUS](state, updating) {
+ state.updatingIgnoreStatus = updating;
+ },
+ [types.SET_UPDATING_RESOLVE_STATUS](state, updating) {
+ state.updatingResolveStatus = updating;
+ },
+};
diff --git a/app/assets/stylesheets/pages/error_details.scss b/app/assets/stylesheets/pages/error_details.scss
index dcd25c126c4..61e2df7ea26 100644
--- a/app/assets/stylesheets/pages/error_details.scss
+++ b/app/assets/stylesheets/pages/error_details.scss
@@ -2,6 +2,11 @@
li {
@include gl-line-height-32;
}
+
+ .btn-outline-info {
+ color: $blue-500;
+ border-color: $blue-500;
+ }
}
.stacktrace {
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index dc06cd8c166..70c4b536854 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -222,7 +222,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def metrics_dashboard_params
params
- .permit(:embedded, :group, :title, :y_label, :dashboard_path, :environment)
+ .permit(:embedded, :group, :title, :y_label, :dashboard_path, :environment, :sample_metrics)
.merge(dashboard_path: params[:dashboard], environment: environment)
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 1d9f81aaa23..76acca3b3bc 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -51,7 +51,7 @@ class ProjectsController < Projects::ApplicationController
def edit
@badge_api_endpoint = expose_url(api_v4_projects_badges_path(id: @project.id))
- render 'edit'
+ render_edit
end
def create
@@ -85,7 +85,7 @@ class ProjectsController < Projects::ApplicationController
else
flash.now[:alert] = result[:message]
- format.html { render 'edit' }
+ format.html { render_edit }
end
format.js
@@ -387,7 +387,6 @@ class ProjectsController < Projects::ApplicationController
:merge_method,
:initialize_with_readme,
:autoclose_referenced_issues,
- :suggestion_commit_message,
project_feature_attributes: %i[
builds_access_level
@@ -488,6 +487,10 @@ class ProjectsController < Projects::ApplicationController
def rate_limiter
::Gitlab::ApplicationRateLimiter
end
+
+ def render_edit
+ render 'edit'
+ end
end
ProjectsController.prepend_if_ee('EE::ProjectsController')
diff --git a/app/helpers/projects/error_tracking_helper.rb b/app/helpers/projects/error_tracking_helper.rb
index f2d16c30fb4..a55f99f9b19 100644
--- a/app/helpers/projects/error_tracking_helper.rb
+++ b/app/helpers/projects/error_tracking_helper.rb
@@ -20,6 +20,7 @@ module Projects::ErrorTrackingHelper
{
'issue-id' => issue_id,
'project-path' => project.full_path,
+ 'list-path' => project_error_tracking_index_path(project),
'issue-details-path' => details_project_error_tracking_index_path(*opts),
'issue-update-path' => update_project_error_tracking_index_path(*opts),
'project-issues-path' => project_issues_path(project),
diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb
index d9a7f0a96dc..cddca72f91f 100644
--- a/app/models/concerns/protected_ref.rb
+++ b/app/models/concerns/protected_ref.rb
@@ -10,6 +10,8 @@ module ProtectedRef
validates :project, presence: true
delegate :matching, :matches?, :wildcard?, to: :ref_matcher
+
+ scope :for_project, ->(project) { where(project: project) }
end
def commit
diff --git a/app/models/diff_viewer/base.rb b/app/models/diff_viewer/base.rb
index 37831683555..75aa51348c8 100644
--- a/app/models/diff_viewer/base.rb
+++ b/app/models/diff_viewer/base.rb
@@ -4,7 +4,7 @@ module DiffViewer
class Base
PARTIAL_PATH_PREFIX = 'projects/diffs/viewers'
- class_attribute :partial_name, :type, :extensions, :file_types, :binary, :switcher_icon, :switcher_title
+ class_attribute :partial_name, :type, :extensions, :binary, :switcher_icon, :switcher_title
# These limits relate to the sum of the old and new blob sizes.
# Limits related to the actual size of the diff are enforced in Gitlab::Diff::File.
@@ -50,7 +50,6 @@ module DiffViewer
return true if blob.nil?
return false if verify_binary && binary? != blob.binary_in_repo?
return true if extensions&.include?(blob.extension)
- return true if file_types&.include?(blob.file_type)
false
end
diff --git a/changelogs/unreleased/118662-drop-support-es-v5-support-v7.yml b/changelogs/unreleased/118662-drop-support-es-v5-support-v7.yml
new file mode 100644
index 00000000000..f06f2b9e95e
--- /dev/null
+++ b/changelogs/unreleased/118662-drop-support-es-v5-support-v7.yml
@@ -0,0 +1,5 @@
+---
+title: Drop support for ES5 add support for ES7
+merge_request: 22859
+author:
+type: added
diff --git a/changelogs/unreleased/14857-activate-promethus-integration-for-projects.yml b/changelogs/unreleased/14857-activate-promethus-integration-for-projects.yml
new file mode 100644
index 00000000000..a83008ee848
--- /dev/null
+++ b/changelogs/unreleased/14857-activate-promethus-integration-for-projects.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate the database to activate projects prometheus service integration for projects with prometheus installed on shared k8s cluster.
+merge_request: 19956
+author:
+type: fixed
diff --git a/changelogs/unreleased/39825-update-sentry-error-status-FE.yml b/changelogs/unreleased/39825-update-sentry-error-status-FE.yml
new file mode 100644
index 00000000000..4ffe4424717
--- /dev/null
+++ b/changelogs/unreleased/39825-update-sentry-error-status-FE.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to ignore/resolve errors from error tracking detail page
+merge_request: 22475
+author:
+type: added
diff --git a/changelogs/unreleased/add-warnings-to-sidekiq-rake-tasks.yml b/changelogs/unreleased/add-warnings-to-sidekiq-rake-tasks.yml
new file mode 100644
index 00000000000..a23819786f6
--- /dev/null
+++ b/changelogs/unreleased/add-warnings-to-sidekiq-rake-tasks.yml
@@ -0,0 +1,5 @@
+---
+title: Add deprecation warning to Rake tasks in sidekiq namespace
+merge_request:
+author:
+type: removed
diff --git a/changelogs/unreleased/include-subgroups-in-group-search.yml b/changelogs/unreleased/include-subgroups-in-group-search.yml
new file mode 100644
index 00000000000..9a5fb6804bc
--- /dev/null
+++ b/changelogs/unreleased/include-subgroups-in-group-search.yml
@@ -0,0 +1,5 @@
+---
+title: Include subgroups when searching inside a group
+merge_request: 22991
+author:
+type: fixed
diff --git a/changelogs/unreleased/return_slug_in_api_services_index.yml b/changelogs/unreleased/return_slug_in_api_services_index.yml
new file mode 100644
index 00000000000..ea3e04c845d
--- /dev/null
+++ b/changelogs/unreleased/return_slug_in_api_services_index.yml
@@ -0,0 +1,5 @@
+---
+title: Add slug to services API response
+merge_request: 22518
+author:
+type: added
diff --git a/changelogs/unreleased/sample-metrics-from-ui.yml b/changelogs/unreleased/sample-metrics-from-ui.yml
new file mode 100644
index 00000000000..e666d4235c0
--- /dev/null
+++ b/changelogs/unreleased/sample-metrics-from-ui.yml
@@ -0,0 +1,5 @@
+---
+title: Backend for allowing sample metrics to be toggled from ui
+merge_request: 22901
+author:
+type: added
diff --git a/config/initializers/elastic_client_setup.rb b/config/initializers/elastic_client_setup.rb
index f38b606b3a8..21745bd81d8 100644
--- a/config/initializers/elastic_client_setup.rb
+++ b/config/initializers/elastic_client_setup.rb
@@ -18,6 +18,32 @@ Gitlab.ee do
Elasticsearch::Model::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
Elasticsearch::Model.singleton_class.prepend GemExtensions::Elasticsearch::Model::Client
+ # This monkey patch cannot be handled by prepend like the above since this
+ # module is included into other classes.
+ module Elasticsearch
+ module Model
+ module Response
+ module Base
+ if Gem::Version.new(Elasticsearch::Model::VERSION) >= Gem::Version.new('7.0.0')
+ raise "elasticsearch-model was upgraded, please remove this monkey patch in #{__FILE__}"
+ end
+
+ # Handle ES7 API where total is returned as an object. This
+ # change is taken from the V7 gem
+ # https://github.com/elastic/elasticsearch-rails/commit/9c40f630e1b549f0b7889fe33dcd826b485af6fc
+ # and can be removed when we upgrade the gem to V7
+ def total
+ if response.response['hits']['total'].respond_to?(:keys)
+ response.response['hits']['total']['value']
+ else
+ response.response['hits']['total']
+ end
+ end
+ end
+ end
+ end
+ end
+
### Modified from elasticsearch-model/lib/elasticsearch/model.rb
[
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 3bc38ef3349..09e6b733bff 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -242,7 +242,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#proxy', as: :prometheus_api
- get '/sample_metrics', to: 'environments/sample_metrics#query' if ENV['USE_SAMPLE_METRICS']
+ get '/sample_metrics', to: 'environments/sample_metrics#query'
end
collection do
diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb
index 8a282562335..880a1211c03 100644
--- a/db/fixtures/development/07_milestones.rb
+++ b/db/fixtures/development/07_milestones.rb
@@ -9,8 +9,7 @@ Gitlab::Seeder.quiet do
state: [:active, :closed].sample,
}
- milestone = Milestones::CreateService.new(
- project, project.team.users.sample, milestone_params).execute
+ Milestones::CreateService.new(project, project.team.users.sample, milestone_params).execute
print '.'
end
diff --git a/db/migrate/20200109085206_create_approval_project_rules_protected_branches.rb b/db/migrate/20200109085206_create_approval_project_rules_protected_branches.rb
new file mode 100644
index 00000000000..4e75f7d41fd
--- /dev/null
+++ b/db/migrate/20200109085206_create_approval_project_rules_protected_branches.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class CreateApprovalProjectRulesProtectedBranches < ActiveRecord::Migration[5.2]
+ DOWNTIME = false
+
+ def change
+ create_table :approval_project_rules_protected_branches, id: false do |t|
+ t.references :approval_project_rule,
+ null: false,
+ index: false,
+ foreign_key: { on_delete: :cascade }
+ t.references :protected_branch,
+ null: false,
+ index: { name: 'index_approval_project_rules_protected_branches_pb_id' },
+ foreign_key: { on_delete: :cascade }
+ t.index [:approval_project_rule_id, :protected_branch_id], name: 'index_approval_project_rules_protected_branches_unique', unique: true, using: :btree
+ end
+ end
+end
diff --git a/db/post_migrate/20200114112932_add_temporary_partial_index_on_project_id_to_services.rb b/db/post_migrate/20200114112932_add_temporary_partial_index_on_project_id_to_services.rb
new file mode 100644
index 00000000000..55494f1e4ac
--- /dev/null
+++ b/db/post_migrate/20200114112932_add_temporary_partial_index_on_project_id_to_services.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddTemporaryPartialIndexOnProjectIdToServices < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ INDEX_NAME = 'tmp_index_on_project_id_partial_with_prometheus_services'
+ PARTIAL_FILTER = "type = 'PrometheusService'"
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :services, :project_id, where: PARTIAL_FILTER, name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index :services, :project_id, where: PARTIAL_FILTER, name: INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20200114113341_patch_prometheus_services_for_shared_cluster_applications.rb b/db/post_migrate/20200114113341_patch_prometheus_services_for_shared_cluster_applications.rb
new file mode 100644
index 00000000000..68361f7b176
--- /dev/null
+++ b/db/post_migrate/20200114113341_patch_prometheus_services_for_shared_cluster_applications.rb
@@ -0,0 +1,88 @@
+# frozen_string_literal: true
+
+class PatchPrometheusServicesForSharedClusterApplications < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ MIGRATION = 'ActivatePrometheusServicesForSharedClusterApplications'.freeze
+ BATCH_SIZE = 500
+ DELAY = 2.minutes
+
+ disable_ddl_transaction!
+
+ module Migratable
+ module Applications
+ class Prometheus < ActiveRecord::Base
+ self.table_name = 'clusters_applications_prometheus'
+
+ enum status: {
+ errored: -1,
+ installed: 3,
+ updated: 5
+ }
+ end
+ end
+
+ class Project < ActiveRecord::Base
+ self.table_name = 'projects'
+ include ::EachBatch
+
+ scope :with_application_on_group_clusters, -> {
+ joins("INNER JOIN namespaces ON namespaces.id = projects.namespace_id")
+ .joins("INNER JOIN cluster_groups ON cluster_groups.group_id = namespaces.id")
+ .joins("INNER JOIN clusters ON clusters.id = cluster_groups.cluster_id AND clusters.cluster_type = #{Cluster.cluster_types['group_type']}")
+ .joins("INNER JOIN clusters_applications_prometheus ON clusters_applications_prometheus.cluster_id = clusters.id
+ AND clusters_applications_prometheus.status IN (#{Applications::Prometheus.statuses[:installed]}, #{Applications::Prometheus.statuses[:updated]})")
+ }
+
+ scope :without_active_prometheus_services, -> {
+ joins("LEFT JOIN services ON services.project_id = projects.id AND services.type = 'PrometheusService'")
+ .where("services.id IS NULL OR (services.active = FALSE AND services.properties = '{}')")
+ }
+ end
+
+ class Cluster < ActiveRecord::Base
+ self.table_name = 'clusters'
+
+ enum cluster_type: {
+ instance_type: 1,
+ group_type: 2
+ }
+
+ def self.has_prometheus_application?
+ joins("INNER JOIN clusters_applications_prometheus ON clusters_applications_prometheus.cluster_id = clusters.id
+ AND clusters_applications_prometheus.status IN (#{Applications::Prometheus.statuses[:installed]}, #{Applications::Prometheus.statuses[:updated]})").exists?
+ end
+ end
+ end
+
+ def up
+ projects_without_active_prometheus_service.group('projects.id').each_batch(of: BATCH_SIZE) do |batch, index|
+ bg_migrations_batch = batch.select('projects.id').map { |project| [MIGRATION, project.id] }
+ delay = index * DELAY
+ BackgroundMigrationWorker.bulk_perform_in(delay.seconds, bg_migrations_batch)
+ end
+ end
+
+ def down
+ # no-op
+ end
+
+ private
+
+ def projects_without_active_prometheus_service
+ scope = Migratable::Project.without_active_prometheus_services
+
+ return scope if migrate_instance_cluster?
+
+ scope.with_application_on_group_clusters
+ end
+
+ def migrate_instance_cluster?
+ if instance_variable_defined?('@migrate_instance_cluster')
+ @migrate_instance_cluster
+ else
+ @migrate_instance_cluster = Migratable::Cluster.instance_type.has_prometheus_application?
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 16b8bf9dda8..f6b815de8ba 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2020_01_13_133352) do
+ActiveRecord::Schema.define(version: 2020_01_14_113341) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -437,6 +437,13 @@ ActiveRecord::Schema.define(version: 2020_01_13_133352) do
t.index ["group_id"], name: "index_approval_project_rules_groups_2"
end
+ create_table "approval_project_rules_protected_branches", id: false, force: :cascade do |t|
+ t.bigint "approval_project_rule_id", null: false
+ t.bigint "protected_branch_id", null: false
+ t.index ["approval_project_rule_id", "protected_branch_id"], name: "index_approval_project_rules_protected_branches_unique", unique: true
+ t.index ["protected_branch_id"], name: "index_approval_project_rules_protected_branches_pb_id"
+ end
+
create_table "approval_project_rules_users", force: :cascade do |t|
t.bigint "approval_project_rule_id", null: false
t.integer "user_id", null: false
@@ -3769,6 +3776,7 @@ ActiveRecord::Schema.define(version: 2020_01_13_133352) do
t.string "description", limit: 500
t.boolean "comment_on_event_enabled", default: true, null: false
t.index ["project_id"], name: "index_services_on_project_id"
+ t.index ["project_id"], name: "tmp_index_on_project_id_partial_with_prometheus_services", where: "((type)::text = 'PrometheusService'::text)"
t.index ["template"], name: "index_services_on_template"
t.index ["type"], name: "index_services_on_type"
end
@@ -4448,6 +4456,8 @@ ActiveRecord::Schema.define(version: 2020_01_13_133352) do
add_foreign_key "approval_project_rules", "projects", on_delete: :cascade
add_foreign_key "approval_project_rules_groups", "approval_project_rules", on_delete: :cascade
add_foreign_key "approval_project_rules_groups", "namespaces", column: "group_id", on_delete: :cascade
+ add_foreign_key "approval_project_rules_protected_branches", "approval_project_rules", on_delete: :cascade
+ add_foreign_key "approval_project_rules_protected_branches", "protected_branches", on_delete: :cascade
add_foreign_key "approval_project_rules_users", "approval_project_rules", on_delete: :cascade
add_foreign_key "approval_project_rules_users", "users", on_delete: :cascade
add_foreign_key "approvals", "merge_requests", name: "fk_310d714958", on_delete: :cascade
diff --git a/doc/api/packages.md b/doc/api/packages.md
index 46e3b8e2d3f..cadd5f0dc75 100644
--- a/doc/api/packages.md
+++ b/doc/api/packages.md
@@ -81,7 +81,15 @@ Example response:
},
"created_at": "2019-11-27T03:37:38.711Z",
"build_info": {
- "pipeline_id": 123
+ "pipeline": {
+ "id": 123,
+ "status": "pending",
+ "ref": "new-pipeline",
+ "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a",
+ "web_url": "https://example.com/foo/bar/pipelines/47",
+ "created_at": "2016-08-11T11:28:34.085Z",
+ "updated_at": "2016-08-11T11:32:35.169Z",
+ }
}
},
{
@@ -95,7 +103,15 @@ Example response:
},
"created_at": "2019-11-27T03:37:38.711Z",
"build_info": {
- "pipeline_id": 123
+ "pipeline": {
+ "id": 123,
+ "status": "pending",
+ "ref": "new-pipeline",
+ "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a",
+ "web_url": "https://example.com/foo/bar/pipelines/47",
+ "created_at": "2016-08-11T11:28:34.085Z",
+ "updated_at": "2016-08-11T11:32:35.169Z",
+ }
}
}
]
@@ -141,7 +157,15 @@ Example response:
},
"created_at": "2019-11-27T03:37:38.711Z",
"build_info": {
- "pipeline_id": 123
+ "pipeline": {
+ "id": 123,
+ "status": "pending",
+ "ref": "new-pipeline",
+ "sha": "a91957a858320c0e17f3a0eca7cfacbff50ea29a",
+ "web_url": "https://example.com/foo/bar/pipelines/47",
+ "created_at": "2016-08-11T11:28:34.085Z",
+ "updated_at": "2016-08-11T11:32:35.169Z",
+ }
}
}
```
diff --git a/doc/api/services.md b/doc/api/services.md
index f11cf7fd1b1..52123320651 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -19,6 +19,7 @@ Example response:
{
"id": 75,
"title": "Jenkins CI",
+ "slug": "jenkins",
"created_at": "2019-11-20T11:20:25.297Z",
"updated_at": "2019-11-20T12:24:37.498Z",
"active": true,
@@ -38,6 +39,7 @@ Example response:
{
"id": 76,
"title": "Alerts endpoint",
+ "slug": "alerts",
"created_at": "2019-11-20T11:20:25.297Z",
"updated_at": "2019-11-20T12:24:37.498Z",
"active": true,
@@ -753,6 +755,7 @@ Example response:
{
"id": 4,
"title": "Slack slash commands",
+ "slug": "slack-slash-commands",
"created_at": "2017-06-27T05:51:39-07:00",
"updated_at": "2017-06-27T05:51:39-07:00",
"active": true,
diff --git a/doc/ci/cloud_deployment/index.md b/doc/ci/cloud_deployment/index.md
index 3f133cd039d..07ffe5439e3 100644
--- a/doc/ci/cloud_deployment/index.md
+++ b/doc/ci/cloud_deployment/index.md
@@ -39,8 +39,25 @@ Some credentials are required to be able to run `aws` commands:
```yml
deploy:
stage: deploy
- image: registry.gitlab.com/gitlab-org/cloud-deploy:latest
+ image: registry.gitlab.com/gitlab-org/cloud-deploy:latest # see the note below
script:
- aws s3 ...
- aws create-deployment ...
```
+
+ NOTE: **Note:**
+ Please note that the image used in the example above
+ (`registry.gitlab.com/gitlab-org/cloud-deploy:latest`) is hosted on the [GitLab
+ Container Registry](../../user/packages/container_registry/index.md) and is
+ ready to use. Alternatively, replace the image with another one hosted on [AWS ECR](#aws-ecr).
+
+### AWS ECR
+
+Instead of referencing an image hosted on the GitLab Registry, you are free to
+reference any other image hosted on any third-party registry, such as
+[Amazon Elastic Container Registry (ECR)](https://aws.amazon.com/ecr).
+
+To do so, please make sure to [push your image into your ECR
+repository](https://docs.aws.amazon.com/AmazonECR/latest/userguide/docker-push-ecr-image.html)
+before referencing it in your `.gitlab-ci.yml` file and replace the `image`
+path to point to your ECR.
diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md
index 7529278f902..09d0d71b3d7 100644
--- a/doc/development/gotchas.md
+++ b/doc/development/gotchas.md
@@ -26,7 +26,7 @@ describe API::Labels do
get api("/projects/#{project.id}/labels", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq('label1')
end
@@ -35,7 +35,7 @@ describe API::Labels do
get api("/projects/#{project.id}/labels", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq('label1')
end
end
@@ -77,7 +77,7 @@ describe API::Labels do
get api("/projects/#{project.id}/labels", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq('foo')
end
@@ -86,7 +86,7 @@ describe API::Labels do
get api("/projects/#{project.id}/labels", user)
- expect(response).to have_http_status(200)
+ expect(response).to have_gitlab_http_status(:ok)
expect(json_response.first['name']).to eq('bar')
end
end
diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md
index 89598e1c8d9..44f32343151 100644
--- a/doc/integration/elasticsearch.md
+++ b/doc/integration/elasticsearch.md
@@ -17,9 +17,10 @@ special searches:
| GitLab version | Elasticsearch version |
| -------------- | --------------------- |
-| GitLab Enterprise Edition 8.4 - 8.17 | Elasticsearch 2.4 with [Delete By Query Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/2.4/plugins-delete-by-query.html) installed |
+| GitLab Enterprise Edition 8.4 - 8.17 | Elasticsearch 2.4 with [Delete By Query Plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/2.4/plugins-delete-by-query.html) installed |
| GitLab Enterprise Edition 9.0 - 11.4 | Elasticsearch 5.1 - 5.5 |
-| GitLab Enterprise Edition 11.5+ | Elasticsearch 5.6 - 6.x |
+| GitLab Enterprise Edition 11.5 - 12.6 | Elasticsearch 5.6 - 6.x |
+| GitLab Enterprise Edition 12.7+ | Elasticsearch 6.x - 7.x |
## Installing Elasticsearch
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 9aa1849869b..d8cc5a9202d 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -786,7 +786,9 @@ A footnote reference tag looks like this:[^1]
Reference tags can use letters and other characters.[^footnote-note]
-[^footnote-note]: Avoid using lowercase `w` or an underscore (`_`) in your tag name until until an [upstream bug](https://gitlab.com/gitlab-org/gitlab/issues/24423) is resolved.
+[^footnote-note]: Avoid using lowercase `w` or an underscore (`_`)
+in your footnote tag name until an
+[upstream bug](https://gitlab.com/gitlab-org/gitlab/issues/24423) is resolved.
```
A footnote reference tag looks like this:[^1]
@@ -795,7 +797,9 @@ A footnote reference tag looks like this:[^1]
Reference tags can use letters and other characters.[^footnote-note]
-[^footnote-note]: Avoid using lowercase `w` or an underscore (`_`) in your tag name until until an [upstream bug](https://gitlab.com/gitlab-org/gitlab/issues/24423) is resolved.
+[^footnote-note]: Avoid using lowercase `w` or an underscore (`_`)
+in your footnote tag name until an
+[upstream bug](https://gitlab.com/gitlab-org/gitlab/issues/24423) is resolved.
### Headers
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 74f8edb0784..a2813d0e063 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -613,6 +613,7 @@ module API
end
class ProtectedBranch < Grape::Entity
+ expose :id
expose :name
expose :push_access_levels, using: Entities::ProtectedRefAccess
expose :merge_access_levels, using: Entities::ProtectedRefAccess
@@ -1128,7 +1129,11 @@ module API
end
class ProjectServiceBasic < Grape::Entity
- expose :id, :title, :created_at, :updated_at, :active
+ expose :id, :title
+ expose :slug do |service|
+ service.to_param.dasherize
+ end
+ expose :created_at, :updated_at, :active
expose :commit_events, :push_events, :issues_events, :confidential_issues_events
expose :merge_requests_events, :tag_push_events, :note_events
expose :confidential_note_events, :pipeline_events, :wiki_page_events
diff --git a/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications.rb b/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications.rb
new file mode 100644
index 00000000000..19f5821d449
--- /dev/null
+++ b/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications.rb
@@ -0,0 +1,52 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Create missing PrometheusServices records or sets active attribute to true
+ # for all projects which belongs to cluster with Prometheus Application installed.
+ class ActivatePrometheusServicesForSharedClusterApplications
+ module Migratable
+ # Migration model namespace isolated from application code.
+ class PrometheusService < ActiveRecord::Base
+ self.inheritance_column = :_type_disabled
+ self.table_name = 'services'
+
+ default_scope { where("services.type = 'PrometheusService'") }
+
+ def self.for_project(project_id)
+ new(
+ project_id: project_id,
+ active: true,
+ properties: '{}',
+ type: 'PrometheusService',
+ template: false,
+ push_events: true,
+ issues_events: true,
+ merge_requests_events: true,
+ tag_push_events: true,
+ note_events: true,
+ category: 'monitoring',
+ default: false,
+ wiki_page_events: true,
+ pipeline_events: true,
+ confidential_issues_events: true,
+ commit_events: true,
+ job_events: true,
+ confidential_note_events: true,
+ deployment_events: false
+ )
+ end
+
+ def managed?
+ properties == '{}'
+ end
+ end
+ end
+
+ def perform(project_id)
+ service = Migratable::PrometheusService.find_by(project_id: project_id) || Migratable::PrometheusService.for_project(project_id)
+ service.update!(active: true) if service.managed?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/gitmodules_parser.rb b/lib/gitlab/git/gitmodules_parser.rb
index 575e12390cd..92940c352d3 100644
--- a/lib/gitlab/git/gitmodules_parser.rb
+++ b/lib/gitlab/git/gitmodules_parser.rb
@@ -71,7 +71,7 @@ module Gitlab
# Convert from an indexed by name to an array indexed by path
# If a submodule doesn't have a path, it is considered bogus
# and is ignored
- submodules_by_name.each_with_object({}) do |(name, data), results|
+ submodules_by_name.each_with_object({}) do |(_name, data), results|
path = data.delete 'path'
next unless path
diff --git a/lib/gitlab/group_search_results.rb b/lib/gitlab/group_search_results.rb
index 334642f252e..8597903ad00 100644
--- a/lib/gitlab/group_search_results.rb
+++ b/lib/gitlab/group_search_results.rb
@@ -30,7 +30,7 @@ module Gitlab
# rubocop:enable CodeReuse/ActiveRecord
def issuable_params
- super.merge(group_id: group.id)
+ super.merge(group_id: group.id, include_subgroups: true)
end
end
end
diff --git a/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb b/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb
index 4f5e9a98799..e085f551952 100644
--- a/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb
+++ b/lib/gitlab/metrics/dashboard/stages/endpoint_inserter.rb
@@ -16,7 +16,7 @@ module Gitlab
private
def endpoint_for_metric(metric)
- if ENV['USE_SAMPLE_METRICS']
+ if params[:sample_metrics]
Gitlab::Routing.url_helpers.sample_metrics_project_environment_path(
project,
params[:environment],
diff --git a/lib/gitlab/sidekiq_config.rb b/lib/gitlab/sidekiq_config.rb
index ffceeb68f20..b246c507e9e 100644
--- a/lib/gitlab/sidekiq_config.rb
+++ b/lib/gitlab/sidekiq_config.rb
@@ -1,78 +1,53 @@
# frozen_string_literal: true
require 'yaml'
-require 'set'
module Gitlab
module SidekiqConfig
- QUEUE_CONFIG_PATHS = begin
- result = %w[app/workers/all_queues.yml]
- result << 'ee/app/workers/all_queues.yml' if Gitlab.ee?
- result
- end.freeze
+ class << self
+ include Gitlab::SidekiqConfig::CliMethods
- # This method is called by `ee/bin/sidekiq-cluster` in EE, which runs outside
- # of bundler/Rails context, so we cannot use any gem or Rails methods.
- def self.worker_queues(rails_path = Rails.root.to_s)
- @worker_queues ||= {}
-
- @worker_queues[rails_path] ||= QUEUE_CONFIG_PATHS.flat_map do |path|
- full_path = File.join(rails_path, path)
-
- File.exist?(full_path) ? YAML.load_file(full_path) : []
+ def redis_queues
+ # Not memoized, because this can change during the life of the application
+ Sidekiq::Queue.all.map(&:name)
end
- end
-
- # This method is called by `ee/bin/sidekiq-cluster` in EE, which runs outside
- # of bundler/Rails context, so we cannot use any gem or Rails methods.
- def self.expand_queues(queues, all_queues = self.worker_queues)
- return [] if queues.empty?
- queues_set = all_queues.to_set
-
- queues.flat_map do |queue|
- [queue, *queues_set.grep(/\A#{queue}:/)]
+ def config_queues
+ @config_queues ||= begin
+ config = YAML.load_file(Rails.root.join('config/sidekiq_queues.yml'))
+ config[:queues].map(&:first)
+ end
end
- end
- def self.redis_queues
- # Not memoized, because this can change during the life of the application
- Sidekiq::Queue.all.map(&:name)
- end
+ def cron_workers
+ @cron_workers ||= Settings.cron_jobs.map { |job_name, options| options['job_class'].constantize }
+ end
- def self.config_queues
- @config_queues ||= begin
- config = YAML.load_file(Rails.root.join('config/sidekiq_queues.yml'))
- config[:queues].map(&:first)
+ def workers
+ @workers ||= begin
+ result = find_workers(Rails.root.join('app', 'workers'))
+ result.concat(find_workers(Rails.root.join('ee', 'app', 'workers'))) if Gitlab.ee?
+ result
+ end
end
- end
- def self.cron_workers
- @cron_workers ||= Settings.cron_jobs.map { |job_name, options| options['job_class'].constantize }
- end
+ private
- def self.workers
- @workers ||= begin
- result = find_workers(Rails.root.join('app', 'workers'))
- result.concat(find_workers(Rails.root.join('ee', 'app', 'workers'))) if Gitlab.ee?
- result
- end
- end
+ def find_workers(root)
+ concerns = root.join('concerns').to_s
- def self.find_workers(root)
- concerns = root.join('concerns').to_s
+ workers = Dir[root.join('**', '*.rb')]
+ .reject { |path| path.start_with?(concerns) }
- workers = Dir[root.join('**', '*.rb')]
- .reject { |path| path.start_with?(concerns) }
+ workers.map! do |path|
+ ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '')
- workers.map! do |path|
- ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '')
+ ns.camelize.constantize
+ end
- ns.camelize.constantize
+ # Skip things that aren't workers
+ workers.select { |w| w < Sidekiq::Worker }
end
-
- # Skip things that aren't workers
- workers.select { |w| w < Sidekiq::Worker }
end
end
end
diff --git a/lib/gitlab/sidekiq_config/cli_methods.rb b/lib/gitlab/sidekiq_config/cli_methods.rb
new file mode 100644
index 00000000000..1ce46289e81
--- /dev/null
+++ b/lib/gitlab/sidekiq_config/cli_methods.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'yaml'
+require 'set'
+
+# These methods are called by `sidekiq-cluster`, which runs outside of
+# the bundler/Rails context, so we cannot use any gem or Rails methods.
+module Gitlab
+ module SidekiqConfig
+ module CliMethods
+ # The methods in this module are used as module methods
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ extend self
+
+ QUEUE_CONFIG_PATHS = begin
+ result = %w[app/workers/all_queues.yml]
+ result << 'ee/app/workers/all_queues.yml' if Gitlab.ee?
+ result
+ end.freeze
+
+ def worker_queues(rails_path = Rails.root.to_s)
+ @worker_queues ||= {}
+
+ @worker_queues[rails_path] ||= QUEUE_CONFIG_PATHS.flat_map do |path|
+ full_path = File.join(rails_path, path)
+
+ File.exist?(full_path) ? YAML.load_file(full_path) : []
+ end
+ end
+
+ def expand_queues(queues, all_queues = self.worker_queues)
+ return [] if queues.empty?
+
+ queues_set = all_queues.to_set
+
+ queues.flat_map do |queue|
+ [queue, *queues_set.grep(/\A#{queue}:/)]
+ end
+ end
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+ end
+ end
+end
diff --git a/lib/tasks/sidekiq.rake b/lib/tasks/sidekiq.rake
index dd9ce86f7ca..cb9f4c751ed 100644
--- a/lib/tasks/sidekiq.rake
+++ b/lib/tasks/sidekiq.rake
@@ -1,21 +1,38 @@
namespace :sidekiq do
- desc "GitLab | Stop sidekiq"
+ def deprecation_warning!
+ warn <<~WARNING
+ This task is deprecated and will be removed in 13.0 as it is thought to be unused.
+
+ If you are using this task, please comment on the below issue:
+ https://gitlab.com/gitlab-org/gitlab/issues/196731
+ WARNING
+ end
+
+ desc "[DEPRECATED] GitLab | Stop sidekiq"
task :stop do
+ deprecation_warning!
+
system(*%w(bin/background_jobs stop))
end
- desc "GitLab | Start sidekiq"
+ desc "[DEPRECATED] GitLab | Start sidekiq"
task :start do
+ deprecation_warning!
+
system(*%w(bin/background_jobs start))
end
- desc 'GitLab | Restart sidekiq'
+ desc '[DEPRECATED] GitLab | Restart sidekiq'
task :restart do
+ deprecation_warning!
+
system(*%w(bin/background_jobs restart))
end
- desc "GitLab | Start sidekiq with launchd on Mac OS X"
+ desc "[DEPRECATED] GitLab | Start sidekiq with launchd on Mac OS X"
task :launchd do
+ deprecation_warning!
+
system(*%w(bin/background_jobs start_no_deamonize))
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9b51ad51a31..84c7535a93f 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7782,6 +7782,9 @@ msgstr ""
msgid "Failed to update environment!"
msgstr ""
+msgid "Failed to update issue status"
+msgstr ""
+
msgid "Failed to update issues, please try again."
msgstr ""
@@ -9778,6 +9781,9 @@ msgstr ""
msgid "Iglu registry URL (optional)"
msgstr ""
+msgid "Ignore"
+msgstr ""
+
msgid "Image %{imageName} was scheduled for deletion from the registry."
msgstr ""
@@ -15566,6 +15572,9 @@ msgstr ""
msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
msgstr ""
+msgid "Resolve"
+msgstr ""
+
msgid "Resolve all threads in new issue"
msgstr ""
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
index 358083d3f1d..98178880be3 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/check_mentions_for_xss_spec.rb
@@ -27,11 +27,10 @@ module QA
Flow::Project.add_member(project: project, username: user.username)
- issue = Resource::Issue.fabricate_via_api! do |issue|
+ Resource::Issue.fabricate_via_api! do |issue|
issue.title = 'issue title'
issue.project = project
- end
- issue.visit!
+ end.visit!
Page::Project::Issue::Show.perform do |show|
show.select_all_activities_filter
diff --git a/rubocop/cop/rspec/have_gitlab_http_status.rb b/rubocop/cop/rspec/have_gitlab_http_status.rb
new file mode 100644
index 00000000000..6b179720060
--- /dev/null
+++ b/rubocop/cop/rspec/have_gitlab_http_status.rb
@@ -0,0 +1,119 @@
+# frozen_string_literal: true
+
+require 'rack/utils'
+
+module RuboCop
+ module Cop
+ module RSpec
+ # This cops checks for `have_http_status` usages in specs.
+ # It also discourages the usage of numeric HTTP status codes in
+ # `have_gitlab_http_status`.
+ #
+ # @example
+ #
+ # # bad
+ # expect(response).to have_http_status(200)
+ # expect(response).to have_http_status(:ok)
+ # expect(response).to have_gitlab_http_status(200)
+ #
+ # # good
+ # expect(response).to have_gitlab_http_status(:ok)
+ #
+ class HaveGitlabHttpStatus < RuboCop::Cop::Cop
+ CODE_TO_SYMBOL = Rack::Utils::SYMBOL_TO_STATUS_CODE.invert
+
+ MSG_MATCHER_NAME =
+ 'Use `have_gitlab_http_status` instead of `have_http_status`.'
+
+ MSG_STATUS =
+ 'Prefer named HTTP status `%{name}` over ' \
+ 'its numeric representation `%{code}`.'
+
+ MSG_UNKNOWN = 'HTTP status `%{code}` is unknown. ' \
+ 'Please provide a valid one or disable this cop.'
+
+ MSG_DOCS_LINK = 'https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#have_gitlab_http_status'
+
+ REPLACEMENT = 'have_gitlab_http_status(%{arg})'
+
+ def_node_matcher :have_http_status?, <<~PATTERN
+ (
+ send nil?
+ {
+ :have_http_status
+ :have_gitlab_http_status
+ }
+ _
+ )
+ PATTERN
+
+ def on_send(node)
+ return unless have_http_status?(node)
+
+ offenses = [
+ offense_for_name(node),
+ offense_for_status(node)
+ ].compact
+
+ return if offenses.empty?
+
+ add_offense(node, message: message_for(offenses))
+ end
+
+ def autocorrect(node)
+ lambda do |corrector|
+ corrector.replace(node.source_range, replacement(node))
+ end
+ end
+
+ private
+
+ def offense_for_name(node)
+ return if method_name(node) == :have_gitlab_http_status
+
+ MSG_MATCHER_NAME
+ end
+
+ def offense_for_status(node)
+ code = extract_numeric_code(node)
+ return unless code
+
+ symbol = code_to_symbol(code)
+ return format(MSG_UNKNOWN, code: code) unless symbol
+
+ format(MSG_STATUS, name: symbol, code: code)
+ end
+
+ def message_for(offenses)
+ (offenses + [MSG_DOCS_LINK]).join(' ')
+ end
+
+ def replacement(node)
+ code = extract_numeric_code(node)
+ arg = code_to_symbol(code) || argument(node).source
+
+ format(REPLACEMENT, arg: arg)
+ end
+
+ def code_to_symbol(code)
+ CODE_TO_SYMBOL[code]&.inspect
+ end
+
+ def extract_numeric_code(node)
+ arg_node = argument(node)
+ return unless arg_node&.type == :int
+
+ arg_node.children[0]
+ end
+
+ def method_name(node)
+ node.children[1]
+ end
+
+ def argument(node)
+ node.children[2]
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/rubocop.rb b/rubocop/rubocop.rb
index 5f95703df01..1479dc3384a 100644
--- a/rubocop/rubocop.rb
+++ b/rubocop/rubocop.rb
@@ -40,6 +40,7 @@ require_relative 'cop/rspec/be_success_matcher'
require_relative 'cop/rspec/env_assignment'
require_relative 'cop/rspec/factories_in_migration_specs'
require_relative 'cop/rspec/top_level_describe_path'
+require_relative 'cop/rspec/have_gitlab_http_status'
require_relative 'cop/qa/element_with_pattern'
require_relative 'cop/qa/ambiguous_page_object_name'
require_relative 'cop/sidekiq_options_queue'
diff --git a/spec/controllers/projects/environments/sample_metrics_controller_spec.rb b/spec/controllers/projects/environments/sample_metrics_controller_spec.rb
index a1fec46d3a0..19b07a2ccc4 100644
--- a/spec/controllers/projects/environments/sample_metrics_controller_spec.rb
+++ b/spec/controllers/projects/environments/sample_metrics_controller_spec.rb
@@ -9,17 +9,6 @@ describe Projects::Environments::SampleMetricsController do
let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:user) { create(:user) }
- before(:context) do
- RSpec::Mocks.with_temporary_scope do
- stub_env('USE_SAMPLE_METRICS', 'true')
- Rails.application.reload_routes!
- end
- end
-
- after(:context) do
- Rails.application.reload_routes!
- end
-
before do
project.add_reporter(user)
sign_in(user)
diff --git a/spec/fixtures/api/schemas/public_api/v4/service.json b/spec/fixtures/api/schemas/public_api/v4/service.json
index a024e38acad..b6f13d1cfe7 100644
--- a/spec/fixtures/api/schemas/public_api/v4/service.json
+++ b/spec/fixtures/api/schemas/public_api/v4/service.json
@@ -3,6 +3,7 @@
"properties": {
"id": { "type": "integer" },
"title": { "type": "string" },
+ "slug": { "type": "string" },
"created_at": { "type": "date-time" },
"updated_at": { "type": "date-time" },
"active": { "type": "boolean" },
diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js
index b5ce9383eb9..d62bda78e9a 100644
--- a/spec/frontend/error_tracking/components/error_details_spec.js
+++ b/spec/frontend/error_tracking/components/error_details_spec.js
@@ -29,6 +29,8 @@ describe('ErrorDetails', () => {
propsData: {
issueId: '123',
projectPath: '/root/gitlab-test',
+ listPath: '/error_tracking',
+ issueUpdatePath: '/123',
issueDetailsPath: '/123/details',
issueStackTracePath: '/stacktrace',
projectIssuesPath: '/test-project/issues/',
@@ -122,6 +124,7 @@ describe('ErrorDetails', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(Stacktrace).exists()).toBe(false);
expect(wrapper.find(GlBadge).exists()).toBe(false);
+ expect(wrapper.findAll('button').length).toBe(3);
});
describe('Badges', () => {
@@ -185,7 +188,7 @@ describe('ErrorDetails', () => {
it('should submit the form', () => {
window.HTMLFormElement.prototype.submit = () => {};
const submitSpy = jest.spyOn(wrapper.vm.$refs.sentryIssueForm, 'submit');
- wrapper.find('button').trigger('click');
+ wrapper.find('[data-qa-selector="create_issue_button"]').trigger('click');
expect(submitSpy).toHaveBeenCalled();
submitSpy.mockRestore();
});
diff --git a/spec/frontend/error_tracking/store/actions_spec.js b/spec/frontend/error_tracking/store/actions_spec.js
new file mode 100644
index 00000000000..8bc53d94345
--- /dev/null
+++ b/spec/frontend/error_tracking/store/actions_spec.js
@@ -0,0 +1,78 @@
+import MockAdapter from 'axios-mock-adapter';
+import testAction from 'helpers/vuex_action_helper';
+import axios from '~/lib/utils/axios_utils';
+import createFlash from '~/flash';
+import * as actions from '~/error_tracking/store/actions';
+import * as types from '~/error_tracking/store/mutation_types';
+import { visitUrl } from '~/lib/utils/url_utility';
+
+jest.mock('~/flash.js');
+jest.mock('~/lib/utils/url_utility');
+
+let mock;
+
+describe('Sentry common store actions', () => {
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ createFlash.mockClear();
+ });
+
+ describe('updateStatus', () => {
+ const endpoint = '123/stacktrace';
+ const redirectUrl = '/list';
+ const status = 'resolved';
+
+ it('should handle successful status update', done => {
+ mock.onPut().reply(200, {});
+ testAction(
+ actions.updateStatus,
+ { endpoint, redirectUrl, status },
+ {},
+ [
+ {
+ payload: true,
+ type: types.SET_UPDATING_RESOLVE_STATUS,
+ },
+ {
+ payload: false,
+ type: 'SET_UPDATING_RESOLVE_STATUS',
+ },
+ ],
+ [],
+ () => {
+ done();
+ expect(visitUrl).toHaveBeenCalledWith(redirectUrl);
+ },
+ );
+ });
+
+ it('should handle unsuccessful status update', done => {
+ mock.onPut().reply(400, {});
+ testAction(
+ actions.updateStatus,
+ { endpoint, redirectUrl, status },
+ {},
+ [
+ {
+ payload: true,
+ type: types.SET_UPDATING_RESOLVE_STATUS,
+ },
+ {
+ payload: false,
+ type: types.SET_UPDATING_RESOLVE_STATUS,
+ },
+ ],
+ [],
+ () => {
+ expect(visitUrl).not.toHaveBeenCalled();
+ expect(createFlash).toHaveBeenCalledTimes(1);
+ done();
+ },
+ );
+ });
+ });
+});
diff --git a/spec/frontend/error_tracking/store/details/actions_spec.js b/spec/frontend/error_tracking/store/details/actions_spec.js
index 0866f76aeef..129760bb705 100644
--- a/spec/frontend/error_tracking/store/details/actions_spec.js
+++ b/spec/frontend/error_tracking/store/details/actions_spec.js
@@ -6,6 +6,8 @@ import * as actions from '~/error_tracking/store/details/actions';
import * as types from '~/error_tracking/store/details/mutation_types';
jest.mock('~/flash.js');
+jest.mock('~/lib/utils/url_utility');
+
let mock;
describe('Sentry error details store actions', () => {
diff --git a/spec/helpers/projects/error_tracking_helper_spec.rb b/spec/helpers/projects/error_tracking_helper_spec.rb
index 7c67448034b..583b1c76d7b 100644
--- a/spec/helpers/projects/error_tracking_helper_spec.rb
+++ b/spec/helpers/projects/error_tracking_helper_spec.rb
@@ -79,6 +79,7 @@ describe Projects::ErrorTrackingHelper do
describe '#error_details_data' do
let(:issue_id) { 1234 }
let(:route_params) { [project.owner, project, issue_id, { format: :json }] }
+ let(:list_path) { project_error_tracking_index_path(project) }
let(:details_path) { details_namespace_project_error_tracking_index_path(*route_params) }
let(:project_path) { project.full_path }
let(:stack_trace_path) { stack_trace_namespace_project_error_tracking_index_path(*route_params) }
@@ -86,6 +87,10 @@ describe Projects::ErrorTrackingHelper do
let(:result) { helper.error_details_data(project, issue_id) }
+ it 'returns the correct list path' do
+ expect(result['list-path']).to eq list_path
+ end
+
it 'returns the correct issue id' do
expect(result['issue-id']).to eq issue_id
end
diff --git a/spec/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications_spec.rb b/spec/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications_spec.rb
new file mode 100644
index 00000000000..0edf87e1354
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/activate_prometheus_services_for_shared_cluster_applications_spec.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::BackgroundMigration::ActivatePrometheusServicesForSharedClusterApplications, :migration, schema: 2020_01_14_113341 do
+ include MigrationHelpers::PrometheusServiceHelpers
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:services) { table(:services) }
+ let(:namespace) { namespaces.create(name: 'user', path: 'user') }
+ let(:project) { projects.create(namespace_id: namespace.id) }
+
+ let(:columns) do
+ %w(project_id active properties type template push_events
+ issues_events merge_requests_events tag_push_events
+ note_events category default wiki_page_events pipeline_events
+ confidential_issues_events commit_events job_events
+ confidential_note_events deployment_events)
+ end
+
+ describe '#perform' do
+ it 'is idempotent' do
+ expect { subject.perform(project.id) }.to change { services.order(:id).map { |row| row.attributes } }
+
+ expect { subject.perform(project.id) }.not_to change { services.order(:id).map { |row| row.attributes } }
+ end
+
+ context 'non prometheus services' do
+ it 'does not change them' do
+ other_type = 'SomeOtherService'
+ services.create(service_params_for(project.id, active: true, type: other_type))
+
+ expect { subject.perform(project.id) }.not_to change { services.where(type: other_type).order(:id).map { |row| row.attributes } }
+ end
+ end
+
+ context 'prometheus services are configured manually ' do
+ it 'does not change them' do
+ properties = '{"api_url":"http://test.dev","manual_configuration":"1"}'
+ services.create(service_params_for(project.id, properties: properties, active: false))
+
+ expect { subject.perform(project.id) }.not_to change { services.order(:id).map { |row| row.attributes } }
+ end
+ end
+
+ context 'prometheus integration services do not exist' do
+ it 'creates missing services entries' do
+ subject.perform(project.id)
+
+ rows = services.order(:id).map { |row| row.attributes.slice(*columns).symbolize_keys }
+
+ expect([service_params_for(project.id, active: true)]).to eq rows
+ end
+ end
+
+ context 'prometheus integration services exist' do
+ context 'in active state' do
+ it 'does not change them' do
+ services.create(service_params_for(project.id, active: true))
+
+ expect { subject.perform(project.id) }.not_to change { services.order(:id).map { |row| row.attributes } }
+ end
+ end
+
+ context 'not in active state' do
+ it 'sets active attribute to true' do
+ service = services.create(service_params_for(project.id))
+
+ expect { subject.perform(project.id) }.to change { service.reload.active? }.from(false).to(true)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb
index 570b0cb7401..746f505c877 100644
--- a/spec/lib/gitlab/group_search_results_spec.rb
+++ b/spec/lib/gitlab/group_search_results_spec.rb
@@ -67,5 +67,11 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq []
end
+
+ it 'sets include_subgroups flag by default' do
+ result = described_class.new(user, anything, group, 'gob')
+
+ expect(result.issuable_params[:include_subgroups]).to eq(true)
+ end
end
end
diff --git a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
index 4fa136bc405..e186a383059 100644
--- a/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/processor_spec.rb
@@ -86,6 +86,16 @@ describe Gitlab::Metrics::Dashboard::Processor do
expect(metrics).to eq %w(metric_b metric_a2 metric_a1)
end
end
+
+ context 'when sample_metrics are requested' do
+ let(:process_params) { [project, dashboard_yml, sequence, { environment: environment, sample_metrics: true }] }
+
+ it 'includes a sample metrics path for the prometheus endpoint with each metric' do
+ expect(all_metrics).to satisfy_all do |metric|
+ metric[:prometheus_endpoint_path] == sample_metrics_path(metric[:id])
+ end
+ end
+ end
end
shared_examples_for 'errors with message' do |expected_message|
@@ -147,4 +157,12 @@ describe Gitlab::Metrics::Dashboard::Processor do
query: query
)
end
+
+ def sample_metrics_path(metric)
+ Gitlab::Routing.url_helpers.sample_metrics_project_environment_path(
+ project,
+ environment,
+ identifier: metric
+ )
+ end
end
diff --git a/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb b/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb
new file mode 100644
index 00000000000..2d12fec5cb3
--- /dev/null
+++ b/spec/migrations/add_temporary_partial_index_on_project_id_to_services_spec.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200114112932_add_temporary_partial_index_on_project_id_to_services.rb')
+
+describe AddTemporaryPartialIndexOnProjectIdToServices, :migration do
+ let(:migration) { described_class.new }
+
+ describe '#up' do
+ it 'creates temporary partial index on type' do
+ expect { migration.up }.to change { migration.index_exists?(:services, :project_id, name: described_class::INDEX_NAME) }.from(false).to(true)
+ end
+ end
+
+ describe '#down' do
+ it 'removes temporary partial index on type' do
+ migration.up
+
+ expect { migration.down }.to change { migration.index_exists?(:services, :project_id, name: described_class::INDEX_NAME) }.from(true).to(false)
+ end
+ end
+end
diff --git a/spec/migrations/patch_prometheus_services_for_shared_cluster_applications_spec.rb b/spec/migrations/patch_prometheus_services_for_shared_cluster_applications_spec.rb
new file mode 100644
index 00000000000..83f994c2a94
--- /dev/null
+++ b/spec/migrations/patch_prometheus_services_for_shared_cluster_applications_spec.rb
@@ -0,0 +1,134 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20200114113341_patch_prometheus_services_for_shared_cluster_applications.rb')
+
+describe PatchPrometheusServicesForSharedClusterApplications, :migration, :sidekiq do
+ include MigrationHelpers::PrometheusServiceHelpers
+
+ let(:namespaces) { table(:namespaces) }
+ let(:projects) { table(:projects) }
+ let(:services) { table(:services) }
+ let(:clusters) { table(:clusters) }
+ let(:cluster_groups) { table(:cluster_groups) }
+ let(:clusters_applications_prometheus) { table(:clusters_applications_prometheus) }
+ let(:namespace) { namespaces.create!(name: 'gitlab', path: 'gitlab-org') }
+
+ let(:application_statuses) do
+ {
+ errored: -1,
+ installed: 3,
+ updated: 5
+ }
+ end
+
+ let(:cluster_types) do
+ {
+ instance_type: 1,
+ group_type: 2
+ }
+ end
+
+ describe '#up' do
+ let!(:project_with_missing_service) { projects.create!(name: 'gitlab', path: 'gitlab-ce', namespace_id: namespace.id) }
+ let(:project_with_inactive_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+ let(:project_with_active_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+ let(:project_with_manual_active_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+ let(:project_with_manual_inactive_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+ let(:project_with_active_not_prometheus_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+ let(:project_with_inactive_not_prometheus_service) { projects.create!(name: 'gitlab', path: 'gitlab-ee', namespace_id: namespace.id) }
+
+ before do
+ services.create(service_params_for(project_with_inactive_service.id, active: false))
+ services.create(service_params_for(project_with_active_service.id, active: true))
+ services.create(service_params_for(project_with_active_not_prometheus_service.id, active: true, type: 'other'))
+ services.create(service_params_for(project_with_inactive_not_prometheus_service.id, active: false, type: 'other'))
+ services.create(service_params_for(project_with_manual_inactive_service.id, active: false, properties: { some: 'data' }.to_json))
+ services.create(service_params_for(project_with_manual_active_service.id, active: true, properties: { some: 'data' }.to_json))
+ end
+
+ shared_examples 'patch prometheus services post migration' do
+ context 'prometheus application is installed on the cluster' do
+ it 'schedules a background migration' do
+ clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:installed], version: '123')
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ background_migrations = [["ActivatePrometheusServicesForSharedClusterApplications", project_with_missing_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_inactive_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_active_not_prometheus_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_inactive_not_prometheus_service.id]]
+
+ migrate!
+
+ enqueued_migrations = BackgroundMigrationWorker.jobs.map { |job| job['args'] }
+ expect(enqueued_migrations).to match_array(background_migrations)
+ end
+ end
+ end
+ end
+
+ context 'prometheus application was recently updated on the cluster' do
+ it 'schedules a background migration' do
+ clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:updated], version: '123')
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ background_migrations = [["ActivatePrometheusServicesForSharedClusterApplications", project_with_missing_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_inactive_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_active_not_prometheus_service.id],
+ ["ActivatePrometheusServicesForSharedClusterApplications", project_with_inactive_not_prometheus_service.id]]
+
+ migrate!
+
+ enqueued_migrations = BackgroundMigrationWorker.jobs.map { |job| job['args'] }
+ expect(enqueued_migrations).to match_array(background_migrations)
+ end
+ end
+ end
+ end
+
+ context 'prometheus application failed to install on the cluster' do
+ it 'does not schedule a background migration' do
+ clusters_applications_prometheus.create(cluster_id: cluster.id, status: application_statuses[:errored], version: '123')
+
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq 0
+ end
+ end
+ end
+ end
+
+ context 'prometheus application is NOT installed on the cluster' do
+ it 'does not schedule a background migration' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ migrate!
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq 0
+ end
+ end
+ end
+ end
+ end
+
+ context 'Cluster is group_type' do
+ let(:cluster) { clusters.create(name: 'cluster', cluster_type: cluster_types[:group_type]) }
+
+ before do
+ cluster_groups.create(group_id: namespace.id, cluster_id: cluster.id)
+ end
+
+ it_behaves_like 'patch prometheus services post migration'
+ end
+
+ context 'Cluster is instance_type' do
+ let(:cluster) { clusters.create(name: 'cluster', cluster_type: cluster_types[:instance_type]) }
+
+ it_behaves_like 'patch prometheus services post migration'
+ end
+ end
+end
diff --git a/spec/models/diff_viewer/base_spec.rb b/spec/models/diff_viewer/base_spec.rb
index 019597993cc..0a1c4c5560e 100644
--- a/spec/models/diff_viewer/base_spec.rb
+++ b/spec/models/diff_viewer/base_spec.rb
@@ -43,34 +43,6 @@ describe DiffViewer::Base do
end
end
- context 'when the file type is supported' do
- let(:commit) { project.commit('1a0b36b3cdad1d2ee32457c102a8c0b7056fa863') }
- let(:diff_file) { commit.diffs.diff_file_with_new_path('LICENSE') }
-
- before do
- viewer_class.file_types = %i(license)
- viewer_class.binary = false
- end
-
- context 'when the binaryness matches' do
- it 'returns true' do
- expect(viewer_class.can_render?(diff_file)).to be_truthy
- end
- end
-
- context 'when the binaryness does not match' do
- before do
- allow_next_instance_of(Blob) do |instance|
- allow(instance).to receive(:binary_in_repo?).and_return(true)
- end
- end
-
- it 'returns false' do
- expect(viewer_class.can_render?(diff_file)).to be_falsey
- end
- end
- end
-
context 'when the extension and file type are not supported' do
it 'returns false' do
expect(viewer_class.can_render?(diff_file)).to be_falsey
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 26407e8a45e..08f58387bf8 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -35,6 +35,7 @@ describe API::Services do
expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array
expect(json_response.count).to eq(1)
+ expect(json_response.first['slug']).to eq('emails-on-push')
expect(response).to match_response_schema('public_api/v4/services')
end
end
@@ -61,6 +62,7 @@ describe API::Services do
put api("/projects/#{project.id}/services/#{dashed_service}?#{query_strings}", user), params: service_attrs
expect(response).to have_gitlab_http_status(200)
+ expect(json_response['slug']).to eq(dashed_service)
events.each do |event|
next if event == "foo"
diff --git a/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb b/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb
new file mode 100644
index 00000000000..12bdacdee3c
--- /dev/null
+++ b/spec/rubocop/cop/rspec/have_gitlab_http_status_spec.rb
@@ -0,0 +1,102 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+require 'rspec-parameterized'
+require 'rubocop'
+require 'rubocop/rspec/support'
+
+require_relative '../../../../rubocop/cop/rspec/have_gitlab_http_status'
+
+describe RuboCop::Cop::RSpec::HaveGitlabHttpStatus do
+ include CopHelper
+
+ using RSpec::Parameterized::TableSyntax
+
+ let(:source_file) { 'spec/foo_spec.rb' }
+
+ subject(:cop) { described_class.new }
+
+ shared_examples 'offense' do |code|
+ it 'registers an offense' do
+ inspect_source(code, source_file)
+
+ expect(cop.offenses.size).to eq(1)
+ expect(cop.offenses.map(&:line)).to eq([1])
+ expect(cop.highlights).to eq([code])
+ end
+ end
+
+ shared_examples 'no offense' do |code|
+ it 'does not register an offense' do
+ inspect_source(code)
+
+ expect(cop.offenses).to be_empty
+ end
+ end
+
+ shared_examples 'autocorrect' do |bad, good|
+ it 'autocorrects' do
+ autocorrected = autocorrect_source(bad, source_file)
+
+ expect(autocorrected).to eql(good)
+ end
+ end
+
+ shared_examples 'no autocorrect' do |code|
+ it 'does not autocorrect' do
+ autocorrected = autocorrect_source(code, source_file)
+
+ expect(autocorrected).to eql(code)
+ end
+ end
+
+ describe 'offenses and autocorrections' do
+ where(:bad, :good) do
+ 'have_http_status(:ok)' | 'have_gitlab_http_status(:ok)'
+ 'have_http_status(204)' | 'have_gitlab_http_status(:no_content)'
+ 'have_gitlab_http_status(201)' | 'have_gitlab_http_status(:created)'
+ 'have_http_status(var)' | 'have_gitlab_http_status(var)'
+ 'have_http_status(:success)' | 'have_gitlab_http_status(:success)'
+ 'have_http_status(:invalid)' | 'have_gitlab_http_status(:invalid)'
+ end
+
+ with_them do
+ include_examples 'offense', params[:bad]
+ include_examples 'no offense', params[:good]
+ include_examples 'autocorrect', params[:bad], params[:good]
+ include_examples 'no autocorrect', params[:good]
+ end
+ end
+
+ describe 'partially autocorrects invalid numeric status' do
+ where(:bad, :good) do
+ 'have_http_status(-1)' | 'have_gitlab_http_status(-1)'
+ end
+
+ with_them do
+ include_examples 'offense', params[:bad]
+ include_examples 'offense', params[:good]
+ include_examples 'autocorrect', params[:bad], params[:good]
+ include_examples 'no autocorrect', params[:good]
+ end
+ end
+
+ describe 'ignore' do
+ where(:code) do
+ [
+ 'have_http_status',
+ 'have_http_status { }',
+ 'have_http_status(200, arg)',
+ 'have_gitlab_http_status',
+ 'have_gitlab_http_status { }',
+ 'have_gitlab_http_status(200, arg)'
+ ]
+ end
+
+ with_them do
+ include_examples 'no offense', params[:code]
+ include_examples 'no autocorrect', params[:code]
+ end
+ end
+end
diff --git a/spec/support/cycle_analytics_helpers/test_generation.rb b/spec/support/cycle_analytics_helpers/test_generation.rb
index 2fac6bfb30b..34018263339 100644
--- a/spec/support/cycle_analytics_helpers/test_generation.rb
+++ b/spec/support/cycle_analytics_helpers/test_generation.rb
@@ -117,7 +117,7 @@ module CycleAnalyticsHelpers
data = data_fn[self]
end_time = rand(1..10).days.from_now
- end_time_conditions.each_with_index do |(condition_name, condition_fn), index|
+ end_time_conditions.each_with_index do |(_condition_name, condition_fn), index|
Timecop.freeze(end_time + index.days) { condition_fn[self, data] }
end
diff --git a/spec/support/migrations_helpers/prometheus_service_helpers.rb b/spec/support/migrations_helpers/prometheus_service_helpers.rb
new file mode 100644
index 00000000000..88f2f71ee1e
--- /dev/null
+++ b/spec/support/migrations_helpers/prometheus_service_helpers.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module MigrationHelpers
+ module PrometheusServiceHelpers
+ def service_params_for(project_id, params = {})
+ {
+ project_id: project_id,
+ active: false,
+ properties: '{}',
+ type: 'PrometheusService',
+ template: false,
+ push_events: true,
+ issues_events: true,
+ merge_requests_events: true,
+ tag_push_events: true,
+ note_events: true,
+ category: 'monitoring',
+ default: false,
+ wiki_page_events: true,
+ pipeline_events: true,
+ confidential_issues_events: true,
+ commit_events: true,
+ job_events: true,
+ confidential_note_events: true,
+ deployment_events: false
+ }.merge(params)
+ end
+
+ def row_attributes(entity)
+ entity.attributes.with_indifferent_access.tap do |hash|
+ hash.merge!(hash.slice(:created_at, :updated_at).transform_values { |v| v.to_s(:db) })
+ end
+ end
+ end
+end