Commit 060c8424 authored by GitLab Bot's avatar GitLab Bot

Add latest changes from gitlab-org/gitlab@master

parent 6867eff1
......@@ -457,9 +457,9 @@ end
# Gitaly GRPC protocol definitions
gem 'gitaly', '~> 12.9.0.pre.rc4'
gem 'grpc', '~> 1.24.0'
gem 'grpc', '~> 1.27.0'
gem 'google-protobuf', '~> 3.8.0'
gem 'google-protobuf', '~> 3.11.2'
gem 'toml-rb', '~> 1.0.0'
......
......@@ -427,7 +427,7 @@ GEM
mime-types (~> 3.0)
representable (~> 3.0)
retriable (>= 2.0, < 4.0)
google-protobuf (3.8.0)
google-protobuf (3.11.4)
googleapis-common-protos-types (1.0.4)
google-protobuf (~> 3.0)
googleauth (0.6.6)
......@@ -468,8 +468,8 @@ GEM
graphql (~> 1.6)
html-pipeline (~> 2.8)
sass (~> 3.4)
grpc (1.24.0)
google-protobuf (~> 3.8)
grpc (1.27.0)
google-protobuf (~> 3.11)
googleapis-common-protos-types (~> 1.0)
gssapi (1.2.0)
ffi (>= 1.0.1)
......@@ -1251,7 +1251,7 @@ DEPENDENCIES
gitlab_omniauth-ldap (~> 2.1.1)
gon (~> 6.2)
google-api-client (~> 0.23)
google-protobuf (~> 3.8.0)
google-protobuf (~> 3.11.2)
gpgme (~> 2.0.19)
grape (~> 1.1.0)
grape-entity (~> 0.7.1)
......@@ -1260,7 +1260,7 @@ DEPENDENCIES
graphiql-rails (~> 1.4.10)
graphql (~> 1.10.5)
graphql-docs (~> 1.6.0)
grpc (~> 1.24.0)
grpc (~> 1.27.0)
gssapi
guard-rspec
haml_lint (~> 0.34.0)
......
......@@ -222,7 +222,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def metrics_dashboard_params
params
.permit(:embedded, :group, :title, :y_label, :dashboard_path, :environment, :sample_metrics)
.permit(:embedded, :group, :title, :y_label, :dashboard_path, :environment, :sample_metrics, :embed_json)
.merge(dashboard_path: params[:dashboard], environment: environment)
end
......
......@@ -3,6 +3,8 @@
class EnvironmentsFinder
attr_reader :project, :current_user, :params
InvalidStatesError = Class.new(StandardError)
def initialize(project, current_user, params = {})
@project, @current_user, @params = project, current_user, params
end
......@@ -45,6 +47,9 @@ class EnvironmentsFinder
environments = by_name(environments)
environments = by_search(environments)
# Raises InvalidStatesError if params[:states] contains invalid states.
environments = by_states(environments)
environments
end
......@@ -91,4 +96,27 @@ class EnvironmentsFinder
environments
end
end
def by_states(environments)
if params[:states].present?
environments_with_states(environments)
else
environments
end
end
def environments_with_states(environments)
# Convert to array of strings
states = Array(params[:states]).map(&:to_s)
raise InvalidStatesError, _('Requested states are invalid') unless valid_states?(states)
environments.with_states(states)
end
def valid_states?(states)
valid_states = Environment.valid_states.map(&:to_s)
(states - valid_states).empty?
end
end
......@@ -10,6 +10,10 @@ module Resolvers
required: false,
description: 'Search query'
argument :states, [GraphQL::STRING_TYPE],
required: false,
description: 'States of environments that should be included in result'
type Types::EnvironmentType, null: true
alias_method :project, :object
......@@ -18,6 +22,8 @@ module Resolvers
return unless project.present?
EnvironmentsFinder.new(project, context[:current_user], args).find
rescue EnvironmentsFinder::InvalidStatesError => exception
raise Gitlab::Graphql::Errors::ArgumentError, exception.message
end
end
end
......@@ -119,6 +119,10 @@ class Environment < ApplicationRecord
find_or_create_by(name: name)
end
def self.valid_states
self.state_machine.states.map(&:name)
end
class << self
##
# This method returns stop actions (jobs) for multiple environments within one
......
......@@ -12,21 +12,48 @@ class UsersStatistics < ApplicationRecord
:blocked
].freeze
private
def highest_role_stats
return unless Feature.enabled?(:users_statistics)
{
owner: batch_count_for_access_level(Gitlab::Access::OWNER),
maintainer: batch_count_for_access_level(Gitlab::Access::MAINTAINER),
developer: batch_count_for_access_level(Gitlab::Access::DEVELOPER),
reporter: batch_count_for_access_level(Gitlab::Access::REPORTER),
guest: batch_count_for_access_level(Gitlab::Access::GUEST)
}
end
class << self
def create_current_stats!
stats_by_role = highest_role_stats
create!(
without_groups_and_projects: without_groups_and_projects_stats,
with_highest_role_guest: stats_by_role[:guest],
with_highest_role_reporter: stats_by_role[:reporter],
with_highest_role_developer: stats_by_role[:developer],
with_highest_role_maintainer: stats_by_role[:maintainer],
with_highest_role_owner: stats_by_role[:owner],
bots: bot_stats,
blocked: blocked_stats
)
end
private
def highest_role_stats
{
owner: batch_count_for_access_level(Gitlab::Access::OWNER),
maintainer: batch_count_for_access_level(Gitlab::Access::MAINTAINER),
developer: batch_count_for_access_level(Gitlab::Access::DEVELOPER),
reporter: batch_count_for_access_level(Gitlab::Access::REPORTER),
guest: batch_count_for_access_level(Gitlab::Access::GUEST)
}
end
def without_groups_and_projects_stats
batch_count_for_access_level(nil)
end
def bot_stats
Gitlab::Database::BatchCount.batch_count(User.bots)
end
def blocked_stats
Gitlab::Database::BatchCount.batch_count(User.blocked)
end
def batch_count_for_access_level(access_level)
Gitlab::Database::BatchCount.batch_count(UserHighestRole.with_highest_access_level(access_level))
def batch_count_for_access_level(access_level)
Gitlab::Database::BatchCount.batch_count(UserHighestRole.with_highest_access_level(access_level))
end
end
end
# frozen_string_literal: true
# Acts as a pass-through to allow embeddable dashboards to be
# generated based on external data, but still processed with the
# required attributes that allow the FE to render them appropriately.
#
# Use Gitlab::Metrics::Dashboard::Finder to retrive dashboards.
module Metrics
module Dashboard
class TransientEmbedService < ::Metrics::Dashboard::BaseEmbedService
extend ::Gitlab::Utils::Override
class << self
def valid_params?(params)
[
embedded?(params[:embedded]),
params[:embed_json]
].all?
end
end
private
override :get_raw_dashboard
def get_raw_dashboard
JSON.parse(params[:embed_json])
end
override :sequence
def sequence
[STAGES::EndpointInserter]
end
end
end
end
......@@ -262,6 +262,13 @@
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: cronjob:users_create_statistics
:feature_category: :users
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
:idempotent:
- :name: deployment:deployments_finished
:feature_category: :continuous_delivery
:has_external_dependencies:
......
# frozen_string_literal: true
module Users
class CreateStatisticsWorker # rubocop:disable Scalability/IdempotentWorker
include ApplicationWorker
# rubocop:disable Scalability/CronWorkerContext
# This worker does not perform work scoped to a context
include CronjobQueue
# rubocop:enable Scalability/CronWorkerContext
feature_category :users
def perform
UsersStatistics.create_current_stats!
rescue ActiveRecord::RecordInvalid => exception
Gitlab::ErrorTracking.track_exception(exception)
end
end
end
---
title: Add ability to search by environment state in environments GraphQL API
merge_request: 28567
author:
type: changed
---
title: Add Fluentd table for cluster apps
merge_request: 28844
author:
type: added
---
title: Add support for database-independent embedded metric charts
merge_request: 28618
author:
type: added
---
title: Add daily job to create users statistics
merge_request: 27883
author:
type: added
......@@ -552,6 +552,9 @@ Gitlab.ee do
Settings.cron_jobs['sync_seat_link_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['sync_seat_link_worker']['cron'] ||= "#{rand(60)} 0 * * *"
Settings.cron_jobs['sync_seat_link_worker']['job_class'] = 'SyncSeatLinkWorker'
Settings.cron_jobs['users_create_statistics_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['users_create_statistics_worker']['cron'] ||= '2 15 * * *'
Settings.cron_jobs['users_create_statistics_worker']['job_class'] = 'Users::CreateStatisticsWorker'
end
#
......
# frozen_string_literal: true
class CreateClustersApplicationsFluentd < ActiveRecord::Migration[6.0]
DOWNTIME = false
def change
create_table :clusters_applications_fluentd do |t|
t.integer :protocol, null: false, limit: 2
t.integer :status, null: false
t.integer :port, null: false
t.references :cluster, null: false, index: { unique: true }, foreign_key: { on_delete: :cascade }
t.timestamps_with_timezone null: false
t.string :version, null: false, limit: 255
t.string :host, null: false, limit: 255
t.text :status_reason
end
end
end
......@@ -1649,6 +1649,28 @@ CREATE SEQUENCE public.clusters_applications_elastic_stacks_id_seq
ALTER SEQUENCE public.clusters_applications_elastic_stacks_id_seq OWNED BY public.clusters_applications_elastic_stacks.id;
CREATE TABLE public.clusters_applications_fluentd (
id bigint NOT NULL,
protocol smallint NOT NULL,
status integer NOT NULL,
port integer NOT NULL,
cluster_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL,
version character varying(255) NOT NULL,
host character varying(255) NOT NULL,
status_reason text
);
CREATE SEQUENCE public.clusters_applications_fluentd_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.clusters_applications_fluentd_id_seq OWNED BY public.clusters_applications_fluentd.id;
CREATE TABLE public.clusters_applications_helm (
id integer NOT NULL,
cluster_id integer NOT NULL,
......@@ -7018,6 +7040,8 @@ ALTER TABLE ONLY public.clusters_applications_crossplane ALTER COLUMN id SET DEF
ALTER TABLE ONLY public.clusters_applications_elastic_stacks ALTER COLUMN id SET DEFAULT nextval('public.clusters_applications_elastic_stacks_id_seq'::regclass);
ALTER TABLE ONLY public.clusters_applications_fluentd ALTER COLUMN id SET DEFAULT nextval('public.clusters_applications_fluentd_id_seq'::regclass);
ALTER TABLE ONLY public.clusters_applications_helm ALTER COLUMN id SET DEFAULT nextval('public.clusters_applications_helm_id_seq'::regclass);
ALTER TABLE ONLY public.clusters_applications_ingress ALTER COLUMN id SET DEFAULT nextval('public.clusters_applications_ingress_id_seq'::regclass);
......@@ -7687,6 +7711,9 @@ ALTER TABLE ONLY public.clusters_applications_crossplane
ALTER TABLE ONLY public.clusters_applications_elastic_stacks
ADD CONSTRAINT clusters_applications_elastic_stacks_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.clusters_applications_fluentd
ADD CONSTRAINT clusters_applications_fluentd_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.clusters_applications_helm
ADD CONSTRAINT clusters_applications_helm_pkey PRIMARY KEY (id);
......@@ -8892,6 +8919,8 @@ CREATE UNIQUE INDEX index_clusters_applications_crossplane_on_cluster_id ON publ
CREATE UNIQUE INDEX index_clusters_applications_elastic_stacks_on_cluster_id ON public.clusters_applications_elastic_stacks USING btree (cluster_id);
CREATE UNIQUE INDEX index_clusters_applications_fluentd_on_cluster_id ON public.clusters_applications_fluentd USING btree (cluster_id);
CREATE UNIQUE INDEX index_clusters_applications_helm_on_cluster_id ON public.clusters_applications_helm USING btree (cluster_id);
CREATE UNIQUE INDEX index_clusters_applications_ingress_on_cluster_id ON public.clusters_applications_ingress USING btree (cluster_id);
......@@ -11164,6 +11193,9 @@ ALTER TABLE ONLY public.ci_refs
ALTER TABLE ONLY public.ci_resources
ADD CONSTRAINT fk_rails_430336af2d FOREIGN KEY (resource_group_id) REFERENCES public.ci_resource_groups(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.clusters_applications_fluentd
ADD CONSTRAINT fk_rails_4319b1dcd2 FOREIGN KEY (cluster_id) REFERENCES public.clusters(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.lfs_file_locks
ADD CONSTRAINT fk_rails_43df7a0412 FOREIGN KEY (project_id) REFERENCES public.projects(id) ON DELETE CASCADE;
......@@ -12976,6 +13008,7 @@ COPY "schema_migrations" (version) FROM STDIN;
20200331220930
20200402123926
20200402135250
20200402185044
20200403184110
20200403185127
20200403185422
......
......@@ -5875,6 +5875,11 @@ type Project {
Search query
"""
search: String
"""
States of environments that should be included in result
"""
states: [String!]
): EnvironmentConnection
"""
......
......@@ -17785,6 +17785,24 @@
},
"defaultValue": null
},
{
"name": "states",
"description": "States of environments that should be included in result",
"type": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
},
"defaultValue": null
},
{
"name": "after",
"description": "Returns the elements in the list that come after the specified cursor.",
......
......@@ -182,7 +182,7 @@ before_script:
## Assuming you created the SSH_KNOWN_HOSTS variable, uncomment the
## following two lines.
##
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
##
......@@ -199,7 +199,7 @@ before_script:
## WARNING: Use this only with the Docker executor, if you use it with shell
## you will overwrite your user's SSH config.
##
#- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
#- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" >> ~/.ssh/config'
```
## Example project
......
......@@ -298,6 +298,9 @@ Some analyzers make it possible to filter out vulnerabilities under a given thre
| `SAST_FLAWFINDER_LEVEL` | 1 | Ignore Flawfinder vulnerabilities under given risk level. Integer, 0=No risk, 5=High risk. |
| `SAST_GITLEAKS_ENTROPY_LEVEL` | 8.0 | Minimum entropy for secret detection. Float, 0.0 = low, 8.0 = high. |
| `SAST_GOSEC_LEVEL` | 0 | Ignore gosec vulnerabilities under given confidence level. Integer, 0=Undefined, 1=Low, 2=Medium, 3=High. |
| `SAST_GITLEAKS_COMMIT_FROM` | - | The commit a gitleaks scan starts at. |
| `SAST_GITLEAKS_COMMIT_TO` | - | The commit a gitleaks scan ends at. |
| `SAST_GITLEAKS_HISTORIC_SCAN` | false | Flag to enable a historic gitleaks scan. |
#### Timeouts
......
......@@ -110,7 +110,7 @@ module API
return unless %w[git-receive-pack git-upload-pack git-upload-archive].include?(action)
{
repository: repository.gitaly_repository,
repository: repository.gitaly_repository.to_h,
address: Gitlab::GitalyClient.address(container.repository_storage),
token: Gitlab::GitalyClient.token(container.repository_storage),
features: Feature::Gitaly.server_feature_flags
......
......@@ -16,6 +16,7 @@ module Gitlab
::Metrics::Dashboard::GitlabAlertEmbedService,
::Metrics::Dashboard::CustomMetricEmbedService,
::Metrics::Dashboard::GrafanaMetricEmbedService,
::Metrics::Dashboard::TransientEmbedService,
::Metrics::Dashboard::DynamicEmbedService,
::Metrics::Dashboard::DefaultEmbedService,
::Metrics::Dashboard::SystemDashboardService,
......
......@@ -16997,6 +16997,9 @@ msgstr ""
msgid "Requested design version does not exist"
msgstr ""
msgid "Requested states are invalid"
msgstr ""
msgid "Requests Profiles"
msgstr ""
......
......@@ -7,6 +7,14 @@ FactoryBot.define do
association :project, :repository
sequence(:external_url) { |n| "https://env#{n}.example.gitlab.com" }
trait :available do
state { :available }
end
trait :stopped do
state { :stopped }
end
trait :with_review_app do |environment|
transient do
ref { 'master' }
......
# frozen_string_literal: true
FactoryBot.define do
factory :users_statistics do
end
end
......@@ -136,6 +136,36 @@ describe 'Metrics rendering', :js, :use_clean_rails_memory_store_caching, :sidek
end
end
context 'transient metrics embeds' do
let(:metrics_url) { urls.metrics_project_environment_url(project, environment, embed_json: embed_json) }
let(:title) { 'Important Metrics' }
let(:embed_json) do
{
panel_groups: [{
panels: [{
type: "line-graph",
title: title,
y_label: "metric",
metrics: [{
query_range: "metric * 0.5 < 1"
}]
}]
}]
}.to_json
end
before do
stub_any_prometheus_request_with_response
end
it 'shows embedded metrics' do
visit project_issue_path(project, issue)
expect(page).to have_css('div.prometheus-graph')
expect(page).to have_text(title)
end
end
def import_common_metrics
::Gitlab::DatabaseImporters::CommonMetrics::Importer.new.execute
end
......
......@@ -3,15 +3,15 @@
require 'spec_helper'
describe EnvironmentsFinder do
describe '#execute' do
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
let(:environment) { create(:environment, project: project) }
let(:project) { create(:project, :repository) }
let(:user) { project.creator }
let(:environment) { create(:environment, :available, project: project) }
before do
project.add_maintainer(user)
end
before do
project.add_maintainer(user)
end
describe '#execute' do
context 'tagged deployment' do
let(:environment_two) { create(:environment, project: project) }
# Environments need to include commits, so rewind two commits to fit
......@@ -124,4 +124,53 @@ describe EnvironmentsFinder do
end
end
end
describe '#find' do
context 'with states parameter' do
let(:stopped_environment) { create(:environment, :stopped, project: project) }
it 'returns environments with the requested state' do
result = described_class.new(project, user, states: 'available').find
expect(result).to contain_exactly(environment)
end
it 'returns environments with any of the requested states' do
result = described_class.new(project, user, states: %w(available stopped)).find
expect(result).to contain_exactly(environment, stopped_environment)
end
it 'raises exception when requested state is invalid' do
expect { described_class.new(project, user, states: %w(invalid stopped)).find }.to(
raise_error(described_class::InvalidStatesError, 'Requested states are invalid')
)
end
context 'works with symbols' do
it 'returns environments with the requested state' do
result = described_class.new(project, user, states: :available).find
expect(result).to contain_exactly(environment)
end
it 'returns environments with any of the requested states' do
result = described_class.new(project, user, states: [:available, :stopped]).find
expect(result).to contain_exactly(environment, stopped_environment)
end
end
end
context 'with search and states' do
let(:environment2) { create(:environment, :stopped, name: 'test2', project: project) }
let(:environment3) { create(:environment, :available, name: 'test3', project: project) }
it 'searches environments by name and state' do
result = described_class.new(project, user, search: 'test', states: :available).find
expect(result).to contain_exactly(environment3)
end
end
end
end
......@@ -10,9 +10,9 @@ describe Resolvers::EnvironmentsResolver do
context "with a group" do
let(:group) { create(:group) }
let(:project) { create(:project, :public, group: group) }
let!(:environment1) { create(:environment, name: 'production', project: project) }
let!(:environment2) { create(:environment, name: 'test', project: project) }
let!(:environment3) { create(:environment, name: 'test2', project: project) }
let!(:environment1) { create(:environment, :available, name: 'production', project: project) }
let!(:environment2) { create(:environment, :stopped, name: 'test', project: project) }
let!(:environment3) { create(:environment, :available, name: 'test2', project: project) }
before do
group.add_developer(current_user)
......@@ -41,6 +41,18 @@ describe Resolvers::EnvironmentsResolver do
end
end
context 'with states' do
it 'searches environments by state' do
expect(resolve_environments(states: ['available'])).to contain_exactly(environment1, environment3)
end
it 'returns error if requested state is invalid' do
expect { resolve_environments(states: ['invalid']) }.to(
raise_error(Gitlab::Graphql::Errors::ArgumentError)
)
end
end
context 'when project is nil' do
subject { resolve(described_class, obj: nil, args: {}, ctx: { current_user: current_user }) }
......
......@@ -98,6 +98,17 @@ describe Gitlab::Metrics::Dashboard::ServiceSelector do
it { is_expected.to be Metrics::Dashboard::GrafanaMetricEmbedService }
end
context 'with the embed defined in the arguments' do
let(:arguments) do
{
embedded: true,
embed_json: '{}'
}
end
it { is_expected.to be Metrics::Dashboard::TransientEmbedService }
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe UsersStatistics do
describe '.create_current_stats!' do
before do
create_list(:user_highest_role, 4)
create_list(:user_highest_role, 2, :guest)
create_list(:user_highest_role, 3, :reporter)
create_list(:user_highest_role, 4, :developer)
create_list(:user_highest_role, 3, :maintainer)
create_list(:user_highest_role, 2, :owner)
create_list(:user, 2, :bot)
create_list(:user, 1, :blocked)
allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false)
end
context 'when successful' do
it 'creates an entry with the current statistics values' do
expect(described_class.create_current_stats!).to have_attributes(
without_groups_and_projects: 4,
with_highest_role_guest: 2,
with_highest_role_reporter: 3,
with_highest_role_developer: 4,
with_highest_role_maintainer: 3,
with_highest_role_owner: 2,
bots: 2,
blocked: 1
)
end
end
context 'when unsuccessful' do
it 'raises an ActiveRecord::RecordInvalid exception' do
allow(UsersStatistics).to receive(:create!).and_raise(ActiveRecord::RecordInvalid)
expect { described_class.create_current_stats! }.to raise_error(ActiveRecord::RecordInvalid)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Metrics::Dashboard::TransientEmbedService, :use_clean_rails_memory_store_caching do
let_it_be(:project) { build(:project) }
let_it_be(:user) { create(:user) }
let_it_be(:environment) { create(:environment, project: project) }
before do
project.add_maintainer(user)
end
describe '.valid_params?' do
let(:params) { { embedded: 'true', embed_json: '{}' } }
subject { described_class.valid_params?(params) }
it { is_expected.to be_truthy }
context 'missing embedded' do
let(:params) { { embed_json: '{}' } }
it { is_expected.to be_falsey }
end
context 'not embedded' do
let(:params) { { embedded: 'false', embed_json: '{}' } }
it { is_expected.to be_falsey }
end
context 'missing embed_json' do
let(:params) { { embedded: 'true' } }
it { is_expected.to be_falsey }
end
end
describe '#get_dashboard' do
let(:embed_json) do
{
panel_groups: [{
panels: [{
type: 'line-graph',
title: 'title',
y_label: 'y_label',
metrics: [{
query_range: 'up',
label: 'y_label'
}]
}]
}]
}.to_json
end
let(:service_params) { [project, user, { environment: environment, embedded: 'true', embed_json: embed_json }] }
let(:service_call) { described_class.new(*service_params).get_dashboard }
it_behaves_like 'valid embedded dashboard service response'
it_behaves_like 'raises error for users with insufficient permissions'
it 'caches the unprocessed dashboard for subsequent calls' do
expect_any_instance_of(described_class)
.to receive(:get_raw_dashboard)
.once
.and_call_original
described_class.new(*service_params).get_dashboard
described_class.new(*service_params).get_dashboard
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Users::CreateStatisticsWorker do
describe '#perform' do
subject { described_class.new.perform }
before do
allow(ActiveRecord::Base.connection).to receive(:transaction_open?).and_return(false)
end
context 'when successful' do
it 'create an users statistics entry' do
expect { subject }.to change { UsersStatistics.count }.from(0).to(1)
end
end
context 'when unsuccessful' do
it 'logs an error' do
users_statistics = build(:users_statistics)
users_statistics.errors.add(:base, 'This is an error')
exception = ActiveRecord::RecordInvalid.new(users_statistics)
allow(UsersStatistics).to receive(:create_current_stats!).and_raise(exception)
expect(Gitlab::ErrorTracking).to receive(:track_exception).with(exception).and_call_original
subject
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