diff options
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 @@ -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 |