From 060c842402c00f830a810702600cbe39dfa6cf62 Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Wed, 8 Apr 2020 00:09:30 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- Gemfile | 4 +- Gemfile.lock | 10 +-- .../projects/environments_controller.rb | 2 +- app/finders/environments_finder.rb | 28 +++++++++ app/graphql/resolvers/environments_resolver.rb | 6 ++ app/models/environment.rb | 4 ++ app/models/users_statistics.rb | 57 ++++++++++++----- .../metrics/dashboard/transient_embed_service.rb | 35 +++++++++++ app/workers/all_queues.yml | 7 +++ app/workers/users/create_statistics_worker.rb | 19 ++++++ changelogs/unreleased/36162-search-by-state.yml | 5 ++ .../unreleased/add_fluentd_cluster_app_table.yml | 5 ++ changelogs/unreleased/sy-transient-embeds.yml | 5 ++ changelogs/unreleased/users_statistics_worker.yml | 5 ++ config/initializers/1_settings.rb | 3 + ...2185044_create_clusters_applications_fluentd.rb | 18 ++++++ db/structure.sql | 33 ++++++++++ doc/api/graphql/reference/gitlab_schema.graphql | 5 ++ doc/api/graphql/reference/gitlab_schema.json | 18 ++++++ doc/ci/ssh_keys/README.md | 4 +- doc/user/application_security/sast/index.md | 3 + lib/api/helpers/internal_helpers.rb | 2 +- lib/gitlab/metrics/dashboard/service_selector.rb | 1 + locale/gitlab.pot | 3 + spec/factories/environments.rb | 8 +++ spec/factories/users_statistics.rb | 6 ++ spec/features/markdown/metrics_spec.rb | 30 +++++++++ spec/finders/environments_finder_spec.rb | 63 ++++++++++++++++--- .../resolvers/environments_resolver_spec.rb | 18 +++++- .../metrics/dashboard/service_selector_spec.rb | 11 ++++ spec/models/users_statistics_spec.rb | 43 +++++++++++++ .../dashboard/transient_embed_service_spec.rb | 72 ++++++++++++++++++++++ .../workers/users/create_statistics_worker_spec.rb | 33 ++++++++++ 33 files changed, 530 insertions(+), 36 deletions(-) create mode 100644 app/services/metrics/dashboard/transient_embed_service.rb create mode 100644 app/workers/users/create_statistics_worker.rb create mode 100644 changelogs/unreleased/36162-search-by-state.yml create mode 100644 changelogs/unreleased/add_fluentd_cluster_app_table.yml create mode 100644 changelogs/unreleased/sy-transient-embeds.yml create mode 100644 changelogs/unreleased/users_statistics_worker.yml create mode 100644 db/migrate/20200402185044_create_clusters_applications_fluentd.rb create mode 100644 spec/factories/users_statistics.rb create mode 100644 spec/models/users_statistics_spec.rb create mode 100644 spec/services/metrics/dashboard/transient_embed_service_spec.rb create mode 100644 spec/workers/users/create_statistics_worker_spec.rb 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 @@ -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.", 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 -- cgit v1.2.1