Commit 571d993b authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 9044365a
...@@ -213,7 +213,7 @@ ...@@ -213,7 +213,7 @@
- name: postgres:9.6 - name: postgres:9.6
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine - name: redis:alpine
- name: elasticsearch:5.6.12 - name: elasticsearch:6.4.2
.use-pg10-ee: .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" 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 @@ ...@@ -221,7 +221,7 @@
- name: postgres:10.9 - name: postgres:10.9
command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"] command: ["postgres", "-c", "fsync=off", "-c", "synchronous_commit=off", "-c", "full_page_writes=off"]
- name: redis:alpine - name: redis:alpine
- name: elasticsearch:5.6.12 - name: elasticsearch:6.4.2
.only-ee: .only-ee:
only: only:
......
...@@ -361,6 +361,9 @@ RSpec/MissingExampleGroupArgument: ...@@ -361,6 +361,9 @@ RSpec/MissingExampleGroupArgument:
RSpec/UnspecifiedException: RSpec/UnspecifiedException:
Enabled: false Enabled: false
RSpec/HaveGitlabHttpStatus:
Enabled: false
Style/MultilineWhenThen: Style/MultilineWhenThen:
Enabled: false Enabled: false
......
...@@ -19,7 +19,7 @@ gem 'default_value_for', '~> 3.3.0' ...@@ -19,7 +19,7 @@ gem 'default_value_for', '~> 3.3.0'
gem 'pg', '~> 1.1' gem 'pg', '~> 1.1'
gem 'rugged', '~> 0.28' gem 'rugged', '~> 0.28'
gem 'grape-path-helpers', '~> 1.1' gem 'grape-path-helpers', '~> 1.2'
gem 'faraday', '~> 0.12' gem 'faraday', '~> 0.12'
gem 'marginalia', '~> 1.8.0' gem 'marginalia', '~> 1.8.0'
......
...@@ -432,7 +432,7 @@ GEM ...@@ -432,7 +432,7 @@ GEM
grape-entity (0.7.1) grape-entity (0.7.1)
activesupport (>= 4.0) activesupport (>= 4.0)
multi_json (>= 1.3.2) multi_json (>= 1.3.2)
grape-path-helpers (1.1.0) grape-path-helpers (1.2.0)
activesupport activesupport
grape (~> 1.0) grape (~> 1.0)
rake (~> 12) rake (~> 12)
...@@ -1230,7 +1230,7 @@ DEPENDENCIES ...@@ -1230,7 +1230,7 @@ DEPENDENCIES
gpgme (~> 2.0.19) gpgme (~> 2.0.19)
grape (~> 1.1.0) grape (~> 1.1.0)
grape-entity (~> 0.7.1) grape-entity (~> 0.7.1)
grape-path-helpers (~> 1.1) grape-path-helpers (~> 1.2)
grape_logging (~> 1.7) grape_logging (~> 1.7)
graphiql-rails (~> 1.4.10) graphiql-rails (~> 1.4.10)
graphql (~> 1.9.11) graphql (~> 1.9.11)
......
...@@ -30,6 +30,14 @@ export default { ...@@ -30,6 +30,14 @@ export default {
}, },
mixins: [timeagoMixin], mixins: [timeagoMixin],
props: { props: {
listPath: {
type: String,
required: true,
},
issueUpdatePath: {
type: String,
required: true,
},
issueId: { issueId: {
type: String, type: String,
required: true, required: true,
...@@ -81,7 +89,14 @@ export default { ...@@ -81,7 +89,14 @@ export default {
}; };
}, },
computed: { computed: {
...mapState('details', ['error', 'loading', 'loadingStacktrace', 'stacktraceData']), ...mapState('details', [
'error',
'loading',
'loadingStacktrace',
'stacktraceData',
'updatingResolveStatus',
'updatingIgnoreStatus',
]),
...mapGetters('details', ['stacktrace']), ...mapGetters('details', ['stacktrace']),
reported() { reported() {
return sprintf( return sprintf(
...@@ -137,12 +152,15 @@ export default { ...@@ -137,12 +152,15 @@ export default {
this.startPollingStacktrace(this.issueStackTracePath); this.startPollingStacktrace(this.issueStackTracePath);
}, },
methods: { methods: {
...mapActions('details', ['startPollingDetails', 'startPollingStacktrace']), ...mapActions('details', ['startPollingDetails', 'startPollingStacktrace', 'updateStatus']),
trackClickErrorLinkToSentryOptions, trackClickErrorLinkToSentryOptions,
createIssue() { createIssue() {
this.issueCreationInProgress = true; this.issueCreationInProgress = true;
this.$refs.sentryIssueForm.submit(); this.$refs.sentryIssueForm.submit();
}, },
updateIssueStatus(status) {
this.updateStatus({ endpoint: this.issueUpdatePath, redirectUrl: this.listPath, status });
},
formatDate(date) { formatDate(date) {
return `${this.timeFormatted(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`; return `${this.timeFormatted(date)} (${dateFormat(date, 'UTC:yyyy-mm-dd h:MM:ssTT Z')})`;
}, },
...@@ -158,24 +176,42 @@ export default { ...@@ -158,24 +176,42 @@ export default {
<div v-else-if="showDetails" class="error-details"> <div v-else-if="showDetails" class="error-details">
<div class="top-area align-items-center justify-content-between py-3"> <div class="top-area align-items-center justify-content-between py-3">
<span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span> <span v-if="!loadingStacktrace && stacktrace" v-html="reported"></span>
<form ref="sentryIssueForm" :action="projectIssuesPath" method="POST"> <div class="d-inline-flex">
<gl-form-input class="hidden" name="issue[title]" :value="issueTitle" /> <loading-button
<input name="issue[description]" :value="issueDescription" type="hidden" /> :label="__('Ignore')"
<gl-form-input :loading="updatingIgnoreStatus"
:value="GQLerror.id" @click="updateIssueStatus('ignored')"
class="hidden"
name="issue[sentry_issue_attributes][sentry_issue_identifier]"
/> />
<gl-form-input :value="csrfToken" class="hidden" name="authenticity_token" />
<loading-button <loading-button
v-if="!error.gitlab_issue" class="btn-outline-info ml-2"
class="btn-success" :label="__('Resolve')"
:label="__('Create issue')" :loading="updatingResolveStatus"
:loading="issueCreationInProgress" @click="updateIssueStatus('resolved')"
data-qa-selector="create_issue_button"
@click="createIssue"
/> />
</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>
<div> <div>
<tooltip-on-truncate :title="GQLerror.title" truncate-target="child" placement="top"> <tooltip-on-truncate :title="GQLerror.title" truncate-target="child" placement="top">
......
...@@ -25,6 +25,8 @@ export default () => { ...@@ -25,6 +25,8 @@ export default () => {
const { const {
issueId, issueId,
projectPath, projectPath,
listPath,
issueUpdatePath,
issueDetailsPath, issueDetailsPath,
issueStackTracePath, issueStackTracePath,
projectIssuesPath, projectIssuesPath,
...@@ -34,6 +36,8 @@ export default () => { ...@@ -34,6 +36,8 @@ export default () => {
props: { props: {
issueId, issueId,
projectPath, projectPath,
listPath,
issueUpdatePath,
issueDetailsPath, issueDetailsPath,
issueStackTracePath, issueStackTracePath,
projectIssuesPath, projectIssuesPath,
......
...@@ -4,4 +4,7 @@ export default { ...@@ -4,4 +4,7 @@ export default {
getSentryData({ endpoint, params }) { getSentryData({ endpoint, params }) {
return axios.get(endpoint, { params }); return axios.get(endpoint, { params });
}, },
updateErrorStatus(endpoint, status) {
return axios.put(endpoint, { status });
},
}; };
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 () => {};
...@@ -3,4 +3,6 @@ export default () => ({ ...@@ -3,4 +3,6 @@ export default () => ({
stacktraceData: {}, stacktraceData: {},
loading: true, loading: true,
loadingStacktrace: true, loadingStacktrace: true,
updatingResolveStatus: false,
updatingIgnoreStatus: false,
}); });
import Vue from 'vue'; import Vue from 'vue';
import Vuex from 'vuex'; import Vuex from 'vuex';
import * as actions from './actions';
import mutations from './mutations';
import * as listActions from './list/actions'; import * as listActions from './list/actions';
import listMutations from './list/mutations'; import listMutations from './list/mutations';
import listState from './list/state'; import listState from './list/state';
...@@ -24,8 +27,8 @@ export const createStore = () => ...@@ -24,8 +27,8 @@ export const createStore = () =>
details: { details: {
namespaced: true, namespaced: true,
state: detailsState(), state: detailsState(),
actions: detailsActions, actions: { ...actions, ...detailsActions },
mutations: detailsMutations, mutations: { ...mutations, ...detailsMutations },
getters: detailsGetters, getters: detailsGetters,
}, },
}, },
......
export const SET_UPDATING_RESOLVE_STATUS = 'SET_UPDATING_RESOLVE_STATUS';
export const SET_UPDATING_IGNORE_STATUS = 'SET_UPDATING_IGNORE_STATUS';
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;
},
};
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
li { li {
@include gl-line-height-32; @include gl-line-height-32;
} }
.btn-outline-info {
color: $blue-500;
border-color: $blue-500;
}
} }
.stacktrace { .stacktrace {
......
...@@ -222,7 +222,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController ...@@ -222,7 +222,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def metrics_dashboard_params def metrics_dashboard_params
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) .merge(dashboard_path: params[:dashboard], environment: environment)
end end
......
...@@ -51,7 +51,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -51,7 +51,7 @@ class ProjectsController < Projects::ApplicationController
def edit def edit
@badge_api_endpoint = expose_url(api_v4_projects_badges_path(id: @project.id)) @badge_api_endpoint = expose_url(api_v4_projects_badges_path(id: @project.id))
render 'edit' render_edit
end end
def create def create
...@@ -85,7 +85,7 @@ class ProjectsController < Projects::ApplicationController ...@@ -85,7 +85,7 @@ class ProjectsController < Projects::ApplicationController
else else
flash.now[:alert] = result[:message] flash.now[:alert] = result[:message]
format.html { render 'edit' } format.html { render_edit }
end end
format.js format.js
...@@ -387,7 +387,6 @@ class ProjectsController < Projects::ApplicationController ...@@ -387,7 +387,6 @@ class ProjectsController < Projects::ApplicationController
:merge_method, :merge_method,
:initialize_with_readme, :initialize_with_readme,
:autoclose_referenced_issues, :autoclose_referenced_issues,
:suggestion_commit_message,
project_feature_attributes: %i[ project_feature_attributes: %i[
builds_access_level builds_access_level
...@@ -488,6 +487,10 @@ class ProjectsController < Projects::ApplicationController ...@@ -488,6 +487,10 @@ class ProjectsController < Projects::ApplicationController
def rate_limiter def rate_limiter
::Gitlab::ApplicationRateLimiter ::Gitlab::ApplicationRateLimiter
end end
def render_edit
render 'edit'
end
end end
ProjectsController.prepend_if_ee('EE::ProjectsController') ProjectsController.prepend_if_ee('EE::ProjectsController')
...@@ -20,6 +20,7 @@ module Projects::ErrorTrackingHelper ...@@ -20,6 +20,7 @@ module Projects::ErrorTrackingHelper
{ {
'issue-id' => issue_id, 'issue-id' => issue_id,
'project-path' => project.full_path, 'project-path' => project.full_path,
'list-path' => project_error_tracking_index_path(project),
'issue-details-path' => details_project_error_tracking_index_path(*opts), 'issue-details-path' => details_project_error_tracking_index_path(*opts),
'issue-update-path' => update_project_error_tracking_index_path(*opts), 'issue-update-path' => update_project_error_tracking_index_path(*opts),
'project-issues-path' => project_issues_path(project), 'project-issues-path' => project_issues_path(project),
......
...@@ -10,6 +10,8 @@ module ProtectedRef ...@@ -10,6 +10,8 @@ module ProtectedRef
validates :project, presence: true validates :project, presence: true
delegate :matching, :matches?, :wildcard?, to: :ref_matcher delegate :matching, :matches?, :wildcard?, to: :ref_matcher
scope :for_project, ->(project) { where(project: project) }
end end
def commit def commit
......
...@@ -4,7 +4,7 @@ module DiffViewer ...@@ -4,7 +4,7 @@ module DiffViewer
class Base class Base
PARTIAL_PATH_PREFIX = 'projects/diffs/viewers' 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. # 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. # Limits related to the actual size of the diff are enforced in Gitlab::Diff::File.
...@@ -50,7 +50,6 @@ module DiffViewer ...@@ -50,7 +50,6 @@ module DiffViewer
return true if blob.nil? return true if blob.nil?
return false if verify_binary && binary? != blob.binary_in_repo? return false if verify_binary && binary? != blob.binary_in_repo?
return true if extensions&.include?(blob.extension) return true if extensions&.include?(blob.extension)
return true if file_types&.include?(blob.file_type)
false false
end end
......
---
title: Drop support for ES5 add support for ES7
merge_request: 22859
author:
type: added
---
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
---
title: Add ability to ignore/resolve errors from error tracking detail page
merge_request: 22475
author:
type: added
---
title: Add deprecation warning to Rake tasks in sidekiq namespace
merge_request:
author:
type: removed
---
title: Include subgroups when searching inside a group
merge_request: 22991
author:
type: fixed
---
title: Add slug to services API response
merge_request: 22518
author:
type: added
---
title: Backend for allowing sample metrics to be toggled from ui
merge_request: 22901
author:
type: added
...@@ -18,6 +18,32 @@ Gitlab.ee do ...@@ -18,6 +18,32 @@ Gitlab.ee do
Elasticsearch::Model::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client Elasticsearch::Model::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
Elasticsearch::Model.singleton_class.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 ### Modified from elasticsearch-model/lib/elasticsearch/model.rb
[ [
......
...@@ -242,7 +242,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -242,7 +242,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get '/prometheus/api/v1/*proxy_path', to: 'environments/prometheus_api#proxy', as: :prometheus_api 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 end
collection do collection do
......
...@@ -9,8 +9,7 @@ Gitlab::Seeder.quiet do ...@@ -9,8 +9,7 @@ Gitlab::Seeder.quiet do
state: [:active, :closed].sample, state: [:active, :closed].sample,
} }
milestone = Milestones::CreateService.new( Milestones::CreateService.new(project, project.team.users.sample, milestone_params).execute
project, project.team.users.sample, milestone_params).execute
print '.' print '.'
end end
......
# 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
# 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
# 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
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm" enable_extension "pg_trgm"
...@@ -437,6 +437,13 @@ ActiveRecord::Schema.define(version: 2020_01_13_133352) do ...@@ -437,6 +437,13 @@ ActiveRecord::Schema.define(version: 2020_01_13_133352) do
t.index ["group_id"], name: "index_approval_project_rules_groups_2" t.index ["group_id"], name: "index_approval_project_rules_groups_2"
end 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| create_table "approval_project_rules_users", force: :cascade do |t|
t.bigint "approval_project_rule_id", null: false t.bigint "approval_project_rule_id", null: false
t.integer "user_id", null: false t.integer "user_id", null: false
...@@ -3769,6 +3776,7 @@ ActiveRecord::Schema.define(version: 2020_01_13_133352) do ...@@ -3769,6 +3776,7 @@ ActiveRecord::Schema.define(version: 2020_01_13_133352) do
t.string "description", limit: 500 t.string "description", limit: 500
t.boolean "comment_on_event_enabled", default: true, null: false 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: "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 ["template"], name: "index_services_on_template"
t.index ["type"], name: "index_services_on_type" t.index ["type"], name: "index_services_on_type"
end end
...@@ -4448,6 +4456,8 @@ ActiveRecord::Schema.define(version: 2020_01_13_133352) do ...@@ -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", "projects", on_delete: :cascade
add_foreign_key "approval_project_rules_groups", "approval_project_rules", 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_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", "approval_project_rules", on_delete: :cascade
add_foreign_key "approval_project_rules_users", "users", 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 add_foreign_key "approvals", "merge_requests", name: "fk_310d714958", on_delete: :cascade
......
...@@ -81,7 +81,15 @@ Example response: ...@@ -81,7 +81,15 @@ Example response:
}, },
"created_at": "2019-11-27T03:37:38.711Z", "created_at": "2019-11-27T03:37:38.711Z",
"build_info": { "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: ...@@ -95,7 +103,15 @@ Example response:
}, },
"created_at": "2019-11-27T03:37:38.711Z", "created_at": "2019-11-27T03:37:38.711Z",
"build_info": { "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: ...@@ -141,7 +157,15 @@ Example response:
}, },
"created_at": "2019-11-27T03:37:38.711Z", "created_at": "2019-11-27T03:37:38.711Z",
"build_info": { "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",
}
} }
} }
``` ```
......
...@@ -19,6 +19,7 @@ Example response: ...@@ -19,6 +19,7 @@ Example response:
{ {
"id": 75, "id": 75,
"title": "Jenkins CI", "title": "Jenkins CI",
"slug": "jenkins",
"created_at": "2019-11-20T11:20:25.297Z", "created_at": "2019-11-20T11:20:25.297Z",
"updated_at": "2019-11-20T12:24:37.498Z", "updated_at": "2019-11-20T12:24:37.498Z",
"active": true, "active": true,
...@@ -38,6 +39,7 @@ Example response: ...@@ -38,6 +39,7 @@ Example response:
{ {
"id": 76, "id": 76,
"title": "Alerts endpoint", "title": "Alerts endpoint",
"slug": "alerts",
"created_at": "2019-11-20T11:20:25.297Z", "created_at": "2019-11-20T11:20:25.297Z",
"updated_at": "2019-11-20T12:24:37.498Z", "updated_at": "2019-11-20T12:24:37.498Z",
"active": true, "active": true,
...@@ -753,6 +755,7 @@ Example response: ...@@ -753,6 +755,7 @@ Example response:
{ {
"id": 4, "id": 4,
"title": "Slack slash commands", "title": "Slack slash commands",
"slug": "slack-slash-commands",
"created_at": "2017-06-27T05:51:39-07:00", "created_at": "2017-06-27T05:51:39-07:00",
"updated_at": "2017-06-27T05:51:39-07:00", "updated_at": "2017-06-27T05:51:39-07:00",
"active": true, "active": true,
......
...@@ -39,8 +39,25 @@ Some credentials are required to be able to run `aws` commands: ...@@ -39,8 +39,25 @@ Some credentials are required to be able to run `aws` commands:
```yml ```yml
deploy: deploy:
stage: 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: script:
- aws s3 ... - aws s3 ...
- aws create-deployment ... - 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.
...@@ -26,7 +26,7 @@ describe API::Labels do ...@@ -26,7 +26,7 @@ describe API::Labels do
get api("/projects/#{project.id}/labels", user) 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') expect(json_response.first['name']).to eq('label1')
end end
...@@ -35,7 +35,7 @@ describe API::Labels do ...@@ -35,7 +35,7 @@ describe API::Labels do
get api("/projects/#{project.id}/labels", user) 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') expect(json_response.first['name']).to eq('label1')
end end
end end
...@@ -77,7 +77,7 @@ describe API::Labels do ...@@ -77,7 +77,7 @@ describe API::Labels do
get api("/projects/#{project.id}/labels", user) 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') expect(json_response.first['name']).to eq('foo')
end end
...@@ -86,7 +86,7 @@ describe API::Labels do ...@@ -86,7 +86,7 @@ describe API::Labels do
get api("/projects/#{project.id}/labels", user) 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') expect(json_response.first['name']).to eq('bar')
end end
end end
......
...@@ -17,9 +17,10 @@ special searches: ...@@ -17,9 +17,10 @@ special searches:
| GitLab version | Elasticsearch version | | 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 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 ## Installing Elasticsearch
......
...@@ -786,7 +786,9 @@ A footnote reference tag looks like this:[^1] ...@@ -786,7 +786,9 @@ A footnote reference tag looks like this:[^1]
Reference tags can use letters and other characters.[^footnote-note] 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] A footnote reference tag looks like this:[^1]
...@@ -795,7 +797,9 @@ 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] 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 ### Headers
......
...@@ -613,6 +613,7 @@ module API ...@@ -613,6 +613,7 @@ module API
end end
class ProtectedBranch < Grape::Entity class ProtectedBranch < Grape::Entity
expose :id
expose :name expose :name
expose :push_access_levels, using: Entities::ProtectedRefAccess expose :push_access_levels, using: Entities::ProtectedRefAccess
expose :merge_access_levels, using: Entities::ProtectedRefAccess expose :merge_access_levels, using: Entities::ProtectedRefAccess
...@@ -1128,7 +1129,11 @@ module API ...@@ -1128,7 +1129,11 @@ module API
end end
class ProjectServiceBasic < Grape::Entity 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 :commit_events, :push_events, :issues_events, :confidential_issues_events
expose :merge_requests_events, :tag_push_events, :note_events expose :merge_requests_events, :tag_push_events, :note_events
expose :confidential_note_events, :pipeline_events, :wiki_page_events expose :confidential_note_events, :pipeline_events, :wiki_page_events
......
# 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
...@@ -71,7 +71,7 @@ module Gitlab ...@@ -71,7 +71,7 @@ module Gitlab
# Convert from an indexed by name to an array indexed by path # Convert from an indexed by name to an array indexed by path
# If a submodule doesn't have a path, it is considered bogus # If a submodule doesn't have a path, it is considered bogus
# and is ignored # 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' path = data.delete 'path'
next unless path next unless path
......
...@@ -30,7 +30,7 @@ module Gitlab ...@@ -30,7 +30,7 @@ module Gitlab
# rubocop:enable CodeReuse/ActiveRecord # rubocop:enable CodeReuse/ActiveRecord
def issuable_params def issuable_params
super.merge(group_id: group.id) super.merge(group_id: group.id, include_subgroups: true)
end end
end end
end end
...@@ -16,7 +16,7 @@ module Gitlab ...@@ -16,7 +16,7 @@ module Gitlab
private private
def endpoint_for_metric(metric) def endpoint_for_metric(metric)
if ENV['USE_SAMPLE_METRICS'] if params[:sample_metrics]
Gitlab::Routing.url_helpers.sample_metrics_project_environment_path( Gitlab::Routing.url_helpers.sample_metrics_project_environment_path(
project, project,
params[:environment], params[:environment],
......
# frozen_string_literal: true # frozen_string_literal: true
require 'yaml' require 'yaml'
require 'set'
module Gitlab module Gitlab
module SidekiqConfig module SidekiqConfig
QUEUE_CONFIG_PATHS = begin class << self
result = %w[app/workers/all_queues.yml] include Gitlab::SidekiqConfig::CliMethods
result << 'ee/app/workers/all_queues.yml' if Gitlab.ee?
result
end.freeze
# This method is called by `ee/bin/sidekiq-cluster` in EE, which runs outside def redis_queues
# of bundler/Rails context, so we cannot use any gem or Rails methods. # Not memoized, because this can change during the life of the application
def self.worker_queues(rails_path = Rails.root.to_s) Sidekiq::Queue.all.map(&:name)
@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
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 def config_queues
@config_queues ||= begin
queues.flat_map do |queue| config = YAML.load_file(Rails.root.join('config/sidekiq_queues.yml'))
[queue, *queues_set.grep(/\A#{queue}:/)] config[:queues].map(&:first)
end
end end
end
def self.redis_queues def cron_workers
# Not memoized, because this can change during the life of the application @cron_workers ||= Settings.cron_jobs.map { |job_name, options| options['job_class'].constantize }
Sidekiq::Queue.all.map(&:name) end
end
def self.config_queues def workers
@config_queues ||= begin @workers ||= begin
config = YAML.load_file(Rails.root.join('config/sidekiq_queues.yml')) result = find_workers(Rails.root.join('app', 'workers'))
config[:queues].map(&:first) result.concat(find_workers(Rails.root.join('ee', 'app', 'workers'))) if Gitlab.ee?
result
end
end end
end
def self.cron_workers private
@cron_workers ||= Settings.cron_jobs.map { |job_name, options| options['job_class'].constantize }
end
def self.workers def find_workers(root)
@workers ||= begin concerns = root.join('concerns').to_s
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 self.find_workers(root) workers = Dir[root.join('**', '*.rb')]
concerns = root.join('concerns').to_s .reject { |path| path.start_with?(concerns) }
workers = Dir[root.join('**', '*.rb')] workers.map! do |path|
.reject { |path| path.start_with?(concerns) } ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '')
workers.map! do |path| ns.camelize.constantize
ns = Pathname.new(path).relative_path_from(root).to_s.gsub('.rb', '') end
ns.camelize.constantize # Skip things that aren't workers
workers.select { |w| w < Sidekiq::Worker }
end end
# Skip things that aren't workers
workers.select { |w| w < Sidekiq::Worker }
end end
end end
end end
# 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
namespace :sidekiq do 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 task :stop do
deprecation_warning!
system(*%w(bin/background_jobs stop)) system(*%w(bin/background_jobs stop))
end end
desc "GitLab | Start sidekiq" desc "[DEPRECATED] GitLab | Start sidekiq"
task :start do task :start do
deprecation_warning!
system(*%w(bin/background_jobs start)) system(*%w(bin/background_jobs start))
end end
desc 'GitLab | Restart sidekiq' desc '[DEPRECATED] GitLab | Restart sidekiq'
task :restart do task :restart do
deprecation_warning!
system(*%w(bin/background_jobs restart)) system(*%w(bin/background_jobs restart))
end 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 task :launchd do
deprecation_warning!
system(*%w(bin/background_jobs start_no_deamonize)) system(*%w(bin/background_jobs start_no_deamonize))
end end
end end
...@@ -7782,6 +7782,9 @@ msgstr "" ...@@ -7782,6 +7782,9 @@ msgstr ""
msgid "Failed to update environment!" msgid "Failed to update environment!"
msgstr "" msgstr ""
msgid "Failed to update issue status"
msgstr ""
msgid "Failed to update issues, please try again." msgid "Failed to update issues, please try again."
msgstr "" msgstr ""
...@@ -9778,6 +9781,9 @@ msgstr "" ...@@ -9778,6 +9781,9 @@ msgstr ""
msgid "Iglu registry URL (optional)" msgid "Iglu registry URL (optional)"
msgstr "" msgstr ""
msgid "Ignore"
msgstr ""
msgid "Image %{imageName} was scheduled for deletion from the registry." msgid "Image %{imageName} was scheduled for deletion from the registry."
msgstr "" msgstr ""
...@@ -15566,6 +15572,9 @@ 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." msgid "Resetting the authorization key will invalidate the previous key. Existing alert configurations will need to be updated with the new key."
msgstr "" msgstr ""
msgid "Resolve"
msgstr ""
msgid "Resolve all threads in new issue" msgid "Resolve all threads in new issue"
msgstr "" msgstr ""
......
...@@ -27,11 +27,10 @@ module QA ...@@ -27,11 +27,10 @@ module QA
Flow::Project.add_member(project: project, username: user.username) 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.title = 'issue title'
issue.project = project issue.project = project
end end.visit!
issue.visit!
Page::Project::Issue::Show.perform do |show| Page::Project::Issue::Show.perform do |show|
show.select_all_activities_filter show.select_all_activities_filter
......
# 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
...@@ -40,6 +40,7 @@ require_relative 'cop/rspec/be_success_matcher' ...@@ -40,6 +40,7 @@ require_relative 'cop/rspec/be_success_matcher'
require_relative 'cop/rspec/env_assignment' require_relative 'cop/rspec/env_assignment'
require_relative 'cop/rspec/factories_in_migration_specs' require_relative 'cop/rspec/factories_in_migration_specs'
require_relative 'cop/rspec/top_level_describe_path' 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/element_with_pattern'
require_relative 'cop/qa/ambiguous_page_object_name' require_relative 'cop/qa/ambiguous_page_object_name'
require_relative 'cop/sidekiq_options_queue' require_relative 'cop/sidekiq_options_queue'
......
...@@ -9,17 +9,6 @@ describe Projects::Environments::SampleMetricsController do ...@@ -9,17 +9,6 @@ describe Projects::Environments::SampleMetricsController do
let_it_be(:environment) { create(:environment, project: project) } let_it_be(:environment) { create(:environment, project: project) }
let_it_be(:user) { create(:user) } 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 before do
project.add_reporter(user) project.add_reporter(user)
sign_in(user) sign_in(user)
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
"properties": { "properties": {
"id": { "type": "integer" }, "id": { "type": "integer" },
"title": { "type": "string" }, "title": { "type": "string" },
"slug": { "type": "string" },
"created_at": { "type": "date-time" }, "created_at": { "type": "date-time" },
"updated_at": { "type": "date-time" }, "updated_at": { "type": "date-time" },
"active": { "type": "boolean" }, "active": { "type": "boolean" },
......
...@@ -29,6 +29,8 @@ describe('ErrorDetails', () => { ...@@ -29,6 +29,8 @@ describe('ErrorDetails', () => {
propsData: { propsData: {
issueId: '123', issueId: '123',
projectPath: '/root/gitlab-test', projectPath: '/root/gitlab-test',
listPath: '/error_tracking',
issueUpdatePath: '/123',
issueDetailsPath: '/123/details', issueDetailsPath: '/123/details',
issueStackTracePath: '/stacktrace', issueStackTracePath: '/stacktrace',
projectIssuesPath: '/test-project/issues/', projectIssuesPath: '/test-project/issues/',
...@@ -122,6 +124,7 @@ describe('ErrorDetails', () => { ...@@ -122,6 +124,7 @@ describe('ErrorDetails', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.find(Stacktrace).exists()).toBe(false); expect(wrapper.find(Stacktrace).exists()).toBe(false);
expect(wrapper.find(GlBadge).exists()).toBe(false); expect(wrapper.find(GlBadge).exists()).toBe(false);
expect(wrapper.findAll('button').length).toBe(3);
}); });
describe('Badges', () => { describe('Badges', () => {
...@@ -185,7 +188,7 @@ describe('ErrorDetails', () => { ...@@ -185,7 +188,7 @@ describe('ErrorDetails', () => {
it('should submit the form', () => { it('should submit the form', () => {
window.HTMLFormElement.prototype.submit = () => {}; window.HTMLFormElement.prototype.submit = () => {};
const submitSpy = jest.spyOn(wrapper.vm.$refs.sentryIssueForm, '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(); expect(submitSpy).toHaveBeenCalled();
submitSpy.mockRestore(); submitSpy.mockRestore();
}); });
......
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();
},
);
});
});
});
...@@ -6,6 +6,8 @@ import * as actions from '~/error_tracking/store/details/actions'; ...@@ -6,6 +6,8 @@ import * as actions from '~/error_tracking/store/details/actions';
import * as types from '~/error_tracking/store/details/mutation_types'; import * as types from '~/error_tracking/store/details/mutation_types';
jest.mock('~/flash.js'); jest.mock('~/flash.js');
jest.mock('~/lib/utils/url_utility');
let mock; let mock;
describe('Sentry error details store actions', () => { describe('Sentry error details store actions', () => {
......
...@@ -79,6 +79,7 @@ describe Projects::ErrorTrackingHelper do ...@@ -79,6 +79,7 @@ describe Projects::ErrorTrackingHelper do
describe '#error_details_data' do describe '#error_details_data' do
let(:issue_id) { 1234 } let(:issue_id) { 1234 }
let(:route_params) { [project.owner, project, issue_id, { format: :json }] } 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(:details_path) { details_namespace_project_error_tracking_index_path(*route_params) }
let(:project_path) { project.full_path } let(:project_path) { project.full_path }
let(:stack_trace_path) { stack_trace_namespace_project_error_tracking_index_path(*route_params) } let(:stack_trace_path) { stack_trace_namespace_project_error_tracking_index_path(*route_params) }
...@@ -86,6 +87,10 @@ describe Projects::ErrorTrackingHelper do ...@@ -86,6 +87,10 @@ describe Projects::ErrorTrackingHelper do
let(:result) { helper.error_details_data(project, issue_id) } 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 it 'returns the correct issue id' do
expect(result['issue-id']).to eq issue_id expect(result['issue-id']).to eq issue_id
end end
......
# 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
...@@ -67,5 +67,11 @@ describe Gitlab::GroupSearchResults do ...@@ -67,5 +67,11 @@ describe Gitlab::GroupSearchResults do
expect(result).to eq [] expect(result).to eq []
end 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
end end
...@@ -86,6 +86,16 @@ describe Gitlab::Metrics::Dashboard::Processor do ...@@ -86,6 +86,16 @@ describe Gitlab::Metrics::Dashboard::Processor do
expect(metrics).to eq %w(metric_b metric_a2 metric_a1) expect(metrics).to eq %w(metric_b metric_a2 metric_a1)
end end
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 end
shared_examples_for 'errors with message' do |expected_message| shared_examples_for 'errors with message' do |expected_message|
...@@ -147,4 +157,12 @@ describe Gitlab::Metrics::Dashboard::Processor do ...@@ -147,4 +157,12 @@ describe Gitlab::Metrics::Dashboard::Processor do
query: query query: query
) )
end end
def sample_metrics_path(metric)
Gitlab::Routing.url_helpers.sample_metrics_project_environment_path(
project,
environment,
identifier: metric
)
end
end end
# 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
# 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
...@@ -43,34 +43,6 @@ describe DiffViewer::Base do ...@@ -43,34 +43,6 @@ describe DiffViewer::Base do
end end
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 context 'when the extension and file type are not supported' do
it 'returns false' do it 'returns false' do
expect(viewer_class.can_render?(diff_file)).to be_falsey expect(viewer_class.can_render?(diff_file)).to be_falsey
......
...@@ -35,6 +35,7 @@ describe API::Services do ...@@ -35,6 +35,7 @@ describe API::Services do
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.count).to eq(1) 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') expect(response).to match_response_schema('public_api/v4/services')
end end
end end
...@@ -61,6 +62,7 @@ describe API::Services do ...@@ -61,6 +62,7 @@ describe API::Services do
put api("/projects/#{project.id}/services/#{dashed_service}?#{query_strings}", user), params: service_attrs put api("/projects/#{project.id}/services/#{dashed_service}?#{query_strings}", user), params: service_attrs
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(200)
expect(json_response['slug']).to eq(dashed_service)
events.each do |event| events.each do |event|
next if event == "foo" next if event == "foo"
......
# 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
...@@ -117,7 +117,7 @@ module CycleAnalyticsHelpers ...@@ -117,7 +117,7 @@ module CycleAnalyticsHelpers
data = data_fn[self] data = data_fn[self]
end_time = rand(1..10).days.from_now 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] } Timecop.freeze(end_time + index.days) { condition_fn[self, data] }
end end
......
# 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
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment