summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock10
-rw-r--r--app/controllers/projects/environments_controller.rb2
-rw-r--r--app/finders/environments_finder.rb28
-rw-r--r--app/graphql/resolvers/environments_resolver.rb6
-rw-r--r--app/models/environment.rb4
-rw-r--r--app/models/users_statistics.rb57
-rw-r--r--app/services/metrics/dashboard/transient_embed_service.rb35
-rw-r--r--app/workers/all_queues.yml7
-rw-r--r--app/workers/users/create_statistics_worker.rb19
-rw-r--r--changelogs/unreleased/36162-search-by-state.yml5
-rw-r--r--changelogs/unreleased/add_fluentd_cluster_app_table.yml5
-rw-r--r--changelogs/unreleased/sy-transient-embeds.yml5
-rw-r--r--changelogs/unreleased/users_statistics_worker.yml5
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--db/migrate/20200402185044_create_clusters_applications_fluentd.rb18
-rw-r--r--db/structure.sql33
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql5
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json18
-rw-r--r--doc/ci/ssh_keys/README.md4
-rw-r--r--doc/user/application_security/sast/index.md3
-rw-r--r--lib/api/helpers/internal_helpers.rb2
-rw-r--r--lib/gitlab/metrics/dashboard/service_selector.rb1
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/factories/environments.rb8
-rw-r--r--spec/factories/users_statistics.rb6
-rw-r--r--spec/features/markdown/metrics_spec.rb30
-rw-r--r--spec/finders/environments_finder_spec.rb63
-rw-r--r--spec/graphql/resolvers/environments_resolver_spec.rb18
-rw-r--r--spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb11
-rw-r--r--spec/models/users_statistics_spec.rb43
-rw-r--r--spec/services/metrics/dashboard/transient_embed_service_spec.rb72
-rw-r--r--spec/workers/users/create_statistics_worker_spec.rb33
33 files changed, 530 insertions, 36 deletions
diff --git a/Gemfile b/Gemfile
index 906709656f3..eab8e2043a5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -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'
diff --git a/Gemfile.lock b/Gemfile.lock
index c65e2252ce6..1f978df8d31 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -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)
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 5c49fa842a4..e51a5c7b84d 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -222,7 +222,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
def metrics_dashboard_params
params
- .permit(:embedded, :group, :title, :y_label, :dashboard_path, :environment, :sample_metrics)
+ .permit(:embedded, :group, :title, :y_label, :dashboard_path, :environment, :sample_metrics, :embed_json)
.merge(dashboard_path: params[:dashboard], environment: environment)
end
diff --git a/app/finders/environments_finder.rb b/app/finders/environments_finder.rb
index 32942c46208..32ca1a42db7 100644
--- a/app/finders/environments_finder.rb
+++ b/app/finders/environments_finder.rb
@@ -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
diff --git a/app/graphql/resolvers/environments_resolver.rb b/app/graphql/resolvers/environments_resolver.rb
index 868abef98eb..4e9a17f1e17 100644
--- a/app/graphql/resolvers/environments_resolver.rb
+++ b/app/graphql/resolvers/environments_resolver.rb
@@ -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
diff --git a/app/models/environment.rb b/app/models/environment.rb
index 23c2296688d..b2391f33aca 100644
--- a/app/models/environment.rb
+++ b/app/models/environment.rb
@@ -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
diff --git a/app/models/users_statistics.rb b/app/models/users_statistics.rb
index 5b4c0ef37d0..1a500717efd 100644
--- a/app/models/users_statistics.rb
+++ b/app/models/users_statistics.rb
@@ -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
diff --git a/app/services/metrics/dashboard/transient_embed_service.rb b/app/services/metrics/dashboard/transient_embed_service.rb
new file mode 100644
index 00000000000..035707dceb9
--- /dev/null
+++ b/app/services/metrics/dashboard/transient_embed_service.rb
@@ -0,0 +1,35 @@
+# 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
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index e37fa52f5ff..3ee8901b23b 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -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:
diff --git a/app/workers/users/create_statistics_worker.rb b/app/workers/users/create_statistics_worker.rb
new file mode 100644
index 00000000000..fb1b192577f
--- /dev/null
+++ b/app/workers/users/create_statistics_worker.rb
@@ -0,0 +1,19 @@
+# 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
diff --git a/changelogs/unreleased/36162-search-by-state.yml b/changelogs/unreleased/36162-search-by-state.yml
new file mode 100644
index 00000000000..a13d385741d
--- /dev/null
+++ b/changelogs/unreleased/36162-search-by-state.yml
@@ -0,0 +1,5 @@
+---
+title: Add ability to search by environment state in environments GraphQL API
+merge_request: 28567
+author:
+type: changed
diff --git a/changelogs/unreleased/add_fluentd_cluster_app_table.yml b/changelogs/unreleased/add_fluentd_cluster_app_table.yml
new file mode 100644
index 00000000000..e6c7ffb2e96
--- /dev/null
+++ b/changelogs/unreleased/add_fluentd_cluster_app_table.yml
@@ -0,0 +1,5 @@
+---
+title: Add Fluentd table for cluster apps
+merge_request: 28844
+author:
+type: added
diff --git a/changelogs/unreleased/sy-transient-embeds.yml b/changelogs/unreleased/sy-transient-embeds.yml
new file mode 100644
index 00000000000..2e2189827bc
--- /dev/null
+++ b/changelogs/unreleased/sy-transient-embeds.yml
@@ -0,0 +1,5 @@
+---
+title: Add support for database-independent embedded metric charts
+merge_request: 28618
+author:
+type: added
diff --git a/changelogs/unreleased/users_statistics_worker.yml b/changelogs/unreleased/users_statistics_worker.yml
new file mode 100644
index 00000000000..08d226d4820
--- /dev/null
+++ b/changelogs/unreleased/users_statistics_worker.yml
@@ -0,0 +1,5 @@
+---
+title: Add daily job to create users statistics
+merge_request: 27883
+author:
+type: added
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 54659ee9c8f..68406049358 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -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
#
diff --git a/db/migrate/20200402185044_create_clusters_applications_fluentd.rb b/db/migrate/20200402185044_create_clusters_applications_fluentd.rb
new file mode 100644
index 00000000000..08f3faf80e1
--- /dev/null
+++ b/db/migrate/20200402185044_create_clusters_applications_fluentd.rb
@@ -0,0 +1,18 @@
+# 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
diff --git a/db/structure.sql b/db/structure.sql
index 9226cdcbc73..9c99d11d8a8 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -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
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 7fd6e78539e..2696c7355bc 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -5875,6 +5875,11 @@ type Project {
Search query
"""
search: String
+
+ """
+ States of environments that should be included in result
+ """
+ states: [String!]
): EnvironmentConnection
"""
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index a4210165c6d..66f4ce0940f 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -17786,6 +17786,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.",
"type": {
diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md
index b3403cd246f..16e87e92a07 100644
--- a/doc/ci/ssh_keys/README.md
+++ b/doc/ci/ssh_keys/README.md
@@ -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
diff --git a/doc/user/application_security/sast/index.md b/doc/user/application_security/sast/index.md
index c864abfe1ae..0930ee6610b 100644
--- a/doc/user/application_security/sast/index.md
+++ b/doc/user/application_security/sast/index.md
@@ -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
diff --git a/lib/api/helpers/internal_helpers.rb b/lib/api/helpers/internal_helpers.rb
index f7aabc8ce4f..003ed229385 100644
--- a/lib/api/helpers/internal_helpers.rb
+++ b/lib/api/helpers/internal_helpers.rb
@@ -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
diff --git a/lib/gitlab/metrics/dashboard/service_selector.rb b/lib/gitlab/metrics/dashboard/service_selector.rb
index b455f955377..49682da320c 100644
--- a/lib/gitlab/metrics/dashboard/service_selector.rb
+++ b/lib/gitlab/metrics/dashboard/service_selector.rb
@@ -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,
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4889e7ce112..01ecfff8ffd 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -16997,6 +16997,9 @@ msgstr ""
msgid "Requested design version does not exist"
msgstr ""
+msgid "Requested states are invalid"
+msgstr ""
+
msgid "Requests Profiles"
msgstr ""
diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb
index 998672ebe7c..050cb8f8e6c 100644
--- a/spec/factories/environments.rb
+++ b/spec/factories/environments.rb
@@ -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' }
diff --git a/spec/factories/users_statistics.rb b/spec/factories/users_statistics.rb
new file mode 100644
index 00000000000..5b0871f2262
--- /dev/null
+++ b/spec/factories/users_statistics.rb
@@ -0,0 +1,6 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :users_statistics do
+ end
+end
diff --git a/spec/features/markdown/metrics_spec.rb b/spec/features/markdown/metrics_spec.rb
index 0d8858a7afd..dadb9571c54 100644
--- a/spec/features/markdown/metrics_spec.rb
+++ b/spec/features/markdown/metrics_spec.rb
@@ -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
diff --git a/spec/finders/environments_finder_spec.rb b/spec/finders/environments_finder_spec.rb
index 850d160506c..66e404f5236 100644
--- a/spec/finders/environments_finder_spec.rb
+++ b/spec/finders/environments_finder_spec.rb
@@ -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
diff --git a/spec/graphql/resolvers/environments_resolver_spec.rb b/spec/graphql/resolvers/environments_resolver_spec.rb
index 35d53c29760..75fd7aff39c 100644
--- a/spec/graphql/resolvers/environments_resolver_spec.rb
+++ b/spec/graphql/resolvers/environments_resolver_spec.rb
@@ -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 }) }
diff --git a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
index 387baf1ee53..245c98cdd00 100644
--- a/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
+++ b/spec/lib/gitlab/metrics/dashboard/service_selector_spec.rb
@@ -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
diff --git a/spec/models/users_statistics_spec.rb b/spec/models/users_statistics_spec.rb
new file mode 100644
index 00000000000..fc23bed711f
--- /dev/null
+++ b/spec/models/users_statistics_spec.rb
@@ -0,0 +1,43 @@
+# 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
diff --git a/spec/services/metrics/dashboard/transient_embed_service_spec.rb b/spec/services/metrics/dashboard/transient_embed_service_spec.rb
new file mode 100644
index 00000000000..fddfbe15281
--- /dev/null
+++ b/spec/services/metrics/dashboard/transient_embed_service_spec.rb
@@ -0,0 +1,72 @@
+# 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
diff --git a/spec/workers/users/create_statistics_worker_spec.rb b/spec/workers/users/create_statistics_worker_spec.rb
new file mode 100644
index 00000000000..3b2b72a832d
--- /dev/null
+++ b/spec/workers/users/create_statistics_worker_spec.rb
@@ -0,0 +1,33 @@
+# 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