diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-21 15:16:55 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-02-21 15:16:55 +0300 |
commit | 0ad1c3fb54e60b8276542d5a401533f7aff92b00 (patch) | |
tree | a1be4d2665e102cc3f805891904d33923586e5bc | |
parent | 0976c2c3d9b722acce50120ea4a09d0e83f50d6e (diff) |
Add latest changes from gitlab-org/gitlab@master
38 files changed, 355 insertions, 160 deletions
diff --git a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue b/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue deleted file mode 100644 index 4c44aac4e2a..00000000000 --- a/app/assets/javascripts/cycle_analytics/components/limit_warning_component.vue +++ /dev/null @@ -1,33 +0,0 @@ -<script> -import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; - -export default { - components: { - GlIcon, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - props: { - count: { - type: Number, - required: true, - }, - }, -}; -</script> -<template> - <span v-if="count === 50" class="events-info float-right"> - <gl-icon - v-gl-tooltip="{ - title: n__( - 'Limited to showing %d event at most', - 'Limited to showing %d events at most', - 50, - ), - }" - name="warning" - /> - {{ n__('Showing %d event', 'Showing %d events', 50) }} - </span> -</template> diff --git a/app/experiments/application_experiment.rb b/app/experiments/application_experiment.rb index f6af7ca15bb..15c7f3389c4 100644 --- a/app/experiments/application_experiment.rb +++ b/app/experiments/application_experiment.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ApplicationExperiment < Gitlab::Experiment + control { nil } # provide a default control for anonymous experiments + def publish(_result = nil) super diff --git a/app/experiments/combined_registration_experiment.rb b/app/experiments/combined_registration_experiment.rb index 576e10815aa..38295cec0d3 100644 --- a/app/experiments/combined_registration_experiment.rb +++ b/app/experiments/combined_registration_experiment.rb @@ -3,6 +3,9 @@ class CombinedRegistrationExperiment < ApplicationExperiment include Rails.application.routes.url_helpers + control { new_users_sign_up_group_path } + candidate { new_users_sign_up_groups_project_path } + def key_for(source, _ = nil) super(source, 'force_company_trial') end @@ -10,12 +13,4 @@ class CombinedRegistrationExperiment < ApplicationExperiment def redirect_path run end - - def control_behavior - new_users_sign_up_group_path - end - - def candidate_behavior - new_users_sign_up_groups_project_path - end end diff --git a/app/experiments/in_product_guidance_environments_webide_experiment.rb b/app/experiments/in_product_guidance_environments_webide_experiment.rb index 6567ec0b3f1..78602874cb7 100644 --- a/app/experiments/in_product_guidance_environments_webide_experiment.rb +++ b/app/experiments/in_product_guidance_environments_webide_experiment.rb @@ -1,11 +1,9 @@ # frozen_string_literal: true class InProductGuidanceEnvironmentsWebideExperiment < ApplicationExperiment - exclude :has_environments? + control { false } - def control_behavior - false - end + exclude :has_environments? private diff --git a/app/experiments/new_project_sast_enabled_experiment.rb b/app/experiments/new_project_sast_enabled_experiment.rb index ee9d0dc1700..4aca4c875b2 100644 --- a/app/experiments/new_project_sast_enabled_experiment.rb +++ b/app/experiments/new_project_sast_enabled_experiment.rb @@ -1,21 +1,15 @@ # frozen_string_literal: true class NewProjectSastEnabledExperiment < ApplicationExperiment - def publish(_result = nil) + control { } + variant(:candidate) { } + variant(:free_indicator) { } + variant(:unchecked_candidate) { } + variant(:unchecked_free_indicator) { } + + def publish(*args) super publish_to_database end - - def candidate_behavior - end - - def free_indicator_behavior - end - - def unchecked_candidate_behavior - end - - def unchecked_free_indicator_behavior - end end diff --git a/app/experiments/require_verification_for_namespace_creation_experiment.rb b/app/experiments/require_verification_for_namespace_creation_experiment.rb index 0c47f5d183c..cb667c6ae60 100644 --- a/app/experiments/require_verification_for_namespace_creation_experiment.rb +++ b/app/experiments/require_verification_for_namespace_creation_experiment.rb @@ -1,18 +1,13 @@ # frozen_string_literal: true class RequireVerificationForNamespaceCreationExperiment < ApplicationExperiment + control { false } + candidate { true } + exclude :existing_user EXPERIMENT_START_DATE = Date.new(2022, 1, 31) - def control_behavior - false - end - - def candidate_behavior - true - end - def candidate? run end diff --git a/app/experiments/security_reports_mr_widget_prompt_experiment.rb b/app/experiments/security_reports_mr_widget_prompt_experiment.rb index bcb9d64fcb7..51b81be672d 100644 --- a/app/experiments/security_reports_mr_widget_prompt_experiment.rb +++ b/app/experiments/security_reports_mr_widget_prompt_experiment.rb @@ -1,14 +1,12 @@ # frozen_string_literal: true class SecurityReportsMrWidgetPromptExperiment < ApplicationExperiment + control { } + candidate { } + def publish(_result = nil) super publish_to_database end - - # This is a purely client side experiment, and since we don't have a nicer - # way to define variants yet, we define them here. - def candidate_behavior - end end diff --git a/app/models/analytics/cycle_analytics/aggregation.rb b/app/models/analytics/cycle_analytics/aggregation.rb new file mode 100644 index 00000000000..b2705f84382 --- /dev/null +++ b/app/models/analytics/cycle_analytics/aggregation.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true +class Analytics::CycleAnalytics::Aggregation < ApplicationRecord + belongs_to :group, optional: false + + validates :incremental_runtimes_in_seconds, :incremental_processed_records, :last_full_run_runtimes_in_seconds, :last_full_run_processed_records, presence: true, length: { maximum: 10 } + + def self.safe_create_for_group(group) + top_level_group = group.root_ancestor + return if Analytics::CycleAnalytics::Aggregation.exists?(group_id: top_level_group.id) + + insert({ group_id: top_level_group.id }, unique_by: :group_id) + end +end diff --git a/app/models/concerns/issue_resource_event.rb b/app/models/concerns/issue_resource_event.rb index 1c24032dbbb..5cbc937e465 100644 --- a/app/models/concerns/issue_resource_event.rb +++ b/app/models/concerns/issue_resource_event.rb @@ -8,6 +8,10 @@ module IssueResourceEvent scope :by_issue, ->(issue) { where(issue_id: issue.id) } - scope :by_issue_ids_and_created_at_earlier_or_equal_to, ->(issue_ids, time) { where(issue_id: issue_ids).where('created_at <= ?', time) } + scope :by_created_at_earlier_or_equal_to, ->(time) { where('created_at <= ?', time) } + scope :by_issue_ids, ->(issue_ids) do + table = self.klass.arel_table + where(table[:issue_id].in(issue_ids)) + end end end diff --git a/config/metrics/license/20210216183237_version.yml b/config/metrics/license/20210216183237_version.yml index 6517c3bc4f5..8d00f03f52b 100644 --- a/config/metrics/license/20210216183237_version.yml +++ b/config/metrics/license/20210216183237_version.yml @@ -6,7 +6,8 @@ product_section: enablement product_stage: enablement product_group: group::distribution product_category: '' -value_type: string +value_type: object +value_json_schema: 'config/metrics/objects_schemas/git_version_schema.json' status: active milestone: "<13.9" time_frame: none diff --git a/config/metrics/objects_schemas/git_version_schema.json b/config/metrics/objects_schemas/git_version_schema.json new file mode 100644 index 00000000000..2489b05e188 --- /dev/null +++ b/config/metrics/objects_schemas/git_version_schema.json @@ -0,0 +1,9 @@ +{ + "type": "object", + "required": ["major"], + "properties": { + "major": { "type": "number", "description": "Major version number" }, + "minor": { "type": "number", "description": "Minor version number" }, + "patch": { "type": "number", "description": "Patch number" } + } +} diff --git a/db/migrate/20220204093120_create_analytics_cycle_analytics_aggregations.rb b/db/migrate/20220204093120_create_analytics_cycle_analytics_aggregations.rb new file mode 100644 index 00000000000..0339e16a85b --- /dev/null +++ b/db/migrate/20220204093120_create_analytics_cycle_analytics_aggregations.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true +class CreateAnalyticsCycleAnalyticsAggregations < Gitlab::Database::Migration[1.0] + enable_lock_retries! + + def up + create_table :analytics_cycle_analytics_aggregations, id: false do |t| + t.references :group, index: false, null: false, foreign_key: { to_table: :namespaces, on_delete: :cascade } + t.integer :incremental_runtimes_in_seconds, array: true, default: [], null: false + t.integer :incremental_processed_records, array: true, default: [], null: false + t.integer :last_full_run_runtimes_in_seconds, array: true, default: [], null: false + t.integer :last_full_run_processed_records, array: true, default: [], null: false + t.integer :last_incremental_issues_id + t.integer :last_incremental_merge_requests_id + t.integer :last_full_run_issues_id + t.integer :last_full_run_merge_requests_id + + t.datetime_with_timezone :last_incremental_run_at + t.datetime_with_timezone :last_incremental_issues_updated_at + t.datetime_with_timezone :last_incremental_merge_requests_updated_at + t.datetime_with_timezone :last_full_run_at + t.datetime_with_timezone :last_full_run_issues_updated_at + t.datetime_with_timezone :last_full_run_mrs_updated_at + t.datetime_with_timezone :last_consistency_check_updated_at + + t.boolean :enabled, default: true, null: false + + t.index :last_incremental_run_at, where: 'enabled IS TRUE', name: 'ca_aggregations_last_incremental_run_at', order: { last_incremental_run_at: 'ASC NULLS FIRST' } + t.index :last_full_run_at, where: 'enabled IS TRUE', name: 'ca_aggregations_last_full_run_at', order: { last_full_run_at: 'ASC NULLS FIRST' } + t.index :last_consistency_check_updated_at, where: 'enabled IS TRUE', name: 'ca_aggregations_last_consistency_check_updated_at', order: { last_consistency_check_updated_at: 'ASC NULLS FIRST' } + + t.check_constraint 'CARDINALITY(incremental_runtimes_in_seconds) <= 10' + t.check_constraint 'CARDINALITY(incremental_processed_records) <= 10' + t.check_constraint 'CARDINALITY(last_full_run_runtimes_in_seconds) <= 10' + t.check_constraint 'CARDINALITY(last_full_run_processed_records) <= 10' + end + + execute("ALTER TABLE analytics_cycle_analytics_aggregations ADD PRIMARY KEY (group_id)") + end + + def down + drop_table :analytics_cycle_analytics_aggregations + end +end diff --git a/db/post_migrate/20220204110725_backfill_cycle_analytics_aggregations.rb b/db/post_migrate/20220204110725_backfill_cycle_analytics_aggregations.rb new file mode 100644 index 00000000000..933ad747c5c --- /dev/null +++ b/db/post_migrate/20220204110725_backfill_cycle_analytics_aggregations.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +class BackfillCycleAnalyticsAggregations < Gitlab::Database::Migration[1.0] + BATCH_SIZE = 50 + + def up + model = define_batchable_model('analytics_cycle_analytics_group_value_streams') + + model.each_batch(of: BATCH_SIZE) do |relation| + execute <<~SQL + WITH records_to_be_inserted AS #{Gitlab::Database::AsWithMaterialized.materialized_if_supported} ( + SELECT root_ancestor.id AS group_id + FROM (#{relation.select(:group_id).to_sql}) as value_streams, + LATERAL ( + WITH RECURSIVE "base_and_ancestors" AS ( + (SELECT "namespaces"."id", "namespaces"."parent_id" FROM "namespaces" WHERE "namespaces"."id" = value_streams.group_id) + UNION + (SELECT "namespaces"."id", "namespaces"."parent_id" FROM "namespaces", "base_and_ancestors" WHERE "namespaces"."id" = "base_and_ancestors"."parent_id") + ) + SELECT "namespaces"."id" FROM "base_and_ancestors" as "namespaces" WHERE parent_id IS NULL LIMIT 1 + ) as root_ancestor + ) + INSERT INTO "analytics_cycle_analytics_aggregations" + SELECT * FROM "records_to_be_inserted" + ON CONFLICT DO NOTHING + SQL + end + end + + def down + # no-op + end +end diff --git a/db/schema_migrations/20220204093120 b/db/schema_migrations/20220204093120 new file mode 100644 index 00000000000..debd48e3c5f --- /dev/null +++ b/db/schema_migrations/20220204093120 @@ -0,0 +1 @@ +e147a8281f98ee397d7d9b652ce21b943e4e87c11fca906b72db839e0e2fa360
\ No newline at end of file diff --git a/db/schema_migrations/20220204110725 b/db/schema_migrations/20220204110725 new file mode 100644 index 00000000000..804dc8c6d54 --- /dev/null +++ b/db/schema_migrations/20220204110725 @@ -0,0 +1 @@ +c87ca83f592c6688c31182fcd4cf6fe282c00a3c92ebe245b66455f57b50fc32
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index f6f1ceb5706..fa2a01e2244 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -10036,6 +10036,30 @@ CREATE SEQUENCE allowed_email_domains_id_seq ALTER SEQUENCE allowed_email_domains_id_seq OWNED BY allowed_email_domains.id; +CREATE TABLE analytics_cycle_analytics_aggregations ( + group_id bigint NOT NULL, + incremental_runtimes_in_seconds integer[] DEFAULT '{}'::integer[] NOT NULL, + incremental_processed_records integer[] DEFAULT '{}'::integer[] NOT NULL, + last_full_run_runtimes_in_seconds integer[] DEFAULT '{}'::integer[] NOT NULL, + last_full_run_processed_records integer[] DEFAULT '{}'::integer[] NOT NULL, + last_incremental_issues_id integer, + last_incremental_merge_requests_id integer, + last_full_run_issues_id integer, + last_full_run_merge_requests_id integer, + last_incremental_run_at timestamp with time zone, + last_incremental_issues_updated_at timestamp with time zone, + last_incremental_merge_requests_updated_at timestamp with time zone, + last_full_run_at timestamp with time zone, + last_full_run_issues_updated_at timestamp with time zone, + last_full_run_mrs_updated_at timestamp with time zone, + last_consistency_check_updated_at timestamp with time zone, + enabled boolean DEFAULT true NOT NULL, + CONSTRAINT chk_rails_1ef688e577 CHECK ((cardinality(incremental_runtimes_in_seconds) <= 10)), + CONSTRAINT chk_rails_7810292ec9 CHECK ((cardinality(last_full_run_processed_records) <= 10)), + CONSTRAINT chk_rails_8b9e89687c CHECK ((cardinality(last_full_run_runtimes_in_seconds) <= 10)), + CONSTRAINT chk_rails_e16bf3913a CHECK ((cardinality(incremental_processed_records) <= 10)) +); + CREATE TABLE analytics_cycle_analytics_group_stages ( id bigint NOT NULL, created_at timestamp with time zone NOT NULL, @@ -22928,6 +22952,9 @@ ALTER TABLE ONLY alert_management_http_integrations ALTER TABLE ONLY allowed_email_domains ADD CONSTRAINT allowed_email_domains_pkey PRIMARY KEY (id); +ALTER TABLE ONLY analytics_cycle_analytics_aggregations + ADD CONSTRAINT analytics_cycle_analytics_aggregations_pkey PRIMARY KEY (group_id); + ALTER TABLE ONLY analytics_cycle_analytics_group_stages ADD CONSTRAINT analytics_cycle_analytics_group_stages_pkey PRIMARY KEY (id); @@ -25243,6 +25270,12 @@ CREATE INDEX approval_mr_rule_index_merge_request_id ON approval_merge_request_r CREATE UNIQUE INDEX bulk_import_trackers_uniq_relation_by_entity ON bulk_import_trackers USING btree (bulk_import_entity_id, relation); +CREATE INDEX ca_aggregations_last_consistency_check_updated_at ON analytics_cycle_analytics_aggregations USING btree (last_consistency_check_updated_at NULLS FIRST) WHERE (enabled IS TRUE); + +CREATE INDEX ca_aggregations_last_full_run_at ON analytics_cycle_analytics_aggregations USING btree (last_full_run_at NULLS FIRST) WHERE (enabled IS TRUE); + +CREATE INDEX ca_aggregations_last_incremental_run_at ON analytics_cycle_analytics_aggregations USING btree (last_incremental_run_at NULLS FIRST) WHERE (enabled IS TRUE); + CREATE INDEX cadence_create_iterations_automation ON iterations_cadences USING btree (automatic, duration_in_weeks, date((COALESCE(last_run_date, '1970-01-01'::date) + ((duration_in_weeks)::double precision * '7 days'::interval)))) WHERE (duration_in_weeks IS NOT NULL); CREATE INDEX ci_builds_gitlab_monitor_metrics ON ci_builds USING btree (status, created_at, project_id) WHERE ((type)::text = 'Ci::Build'::text); @@ -30367,6 +30400,9 @@ ALTER TABLE ONLY bulk_imports ALTER TABLE ONLY diff_note_positions ADD CONSTRAINT fk_rails_13c7212859 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE; +ALTER TABLE ONLY analytics_cycle_analytics_aggregations + ADD CONSTRAINT fk_rails_13c8374c7a FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY users_security_dashboard_projects ADD CONSTRAINT fk_rails_150cd5682c FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index f126c828240..a760c6e4e1a 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -11942,7 +11942,6 @@ Represents an iteration object. | <a id="iterationid"></a>`id` | [`ID!`](#id) | ID of the iteration. | | <a id="iterationiid"></a>`iid` | [`ID!`](#id) | Internal ID of the iteration. | | <a id="iterationiterationcadence"></a>`iterationCadence` | [`IterationCadence!`](#iterationcadence) | Cadence of the iteration. | -| <a id="iterationreport"></a>`report` | [`TimeboxReport`](#timeboxreport) | Historically accurate report about the timebox. | | <a id="iterationscopedpath"></a>`scopedPath` | [`String`](#string) | Web path of the iteration, scoped to the query parent. Only valid for Project parents. Returns null in other contexts. | | <a id="iterationscopedurl"></a>`scopedUrl` | [`String`](#string) | Web URL of the iteration, scoped to the query parent. Only valid for Project parents. Returns null in other contexts. | | <a id="iterationsequence"></a>`sequence` | [`Int!`](#int) | Sequence number for the iteration when you sort the containing cadence's iterations by the start and end date. The earliest starting and ending iteration is assigned 1. | @@ -11953,6 +11952,20 @@ Represents an iteration object. | <a id="iterationwebpath"></a>`webPath` | [`String!`](#string) | Web path of the iteration. | | <a id="iterationweburl"></a>`webUrl` | [`String!`](#string) | Web URL of the iteration. | +#### Fields with arguments + +##### `Iteration.report` + +Historically accurate report about the timebox. + +Returns [`TimeboxReport`](#timeboxreport). + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="iterationreportfullpath"></a>`fullPath` | [`String`](#string) | Full path of the project or group used as a scope for report. For example, `gitlab-org` or `gitlab-org/gitlab`. | + ### `IterationCadence` Represents an iteration cadence. @@ -12875,7 +12888,6 @@ Represents a milestone. | <a id="milestoneid"></a>`id` | [`ID!`](#id) | ID of the milestone. | | <a id="milestoneiid"></a>`iid` | [`ID!`](#id) | Internal ID of the milestone. | | <a id="milestoneprojectmilestone"></a>`projectMilestone` | [`Boolean!`](#boolean) | Indicates if milestone is at project level. | -| <a id="milestonereport"></a>`report` | [`TimeboxReport`](#timeboxreport) | Historically accurate report about the timebox. | | <a id="milestonestartdate"></a>`startDate` | [`Time`](#time) | Timestamp of the milestone start date. | | <a id="milestonestate"></a>`state` | [`MilestoneStateEnum!`](#milestonestateenum) | State of the milestone. | | <a id="milestonestats"></a>`stats` | [`MilestoneStats`](#milestonestats) | Milestone statistics. | @@ -12884,6 +12896,20 @@ Represents a milestone. | <a id="milestoneupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of last milestone update. | | <a id="milestonewebpath"></a>`webPath` | [`String!`](#string) | Web path of the milestone. | +#### Fields with arguments + +##### `Milestone.report` + +Historically accurate report about the timebox. + +Returns [`TimeboxReport`](#timeboxreport). + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="milestonereportfullpath"></a>`fullPath` | [`String`](#string) | Full path of the project or group used as a scope for report. For example, `gitlab-org` or `gitlab-org/gitlab`. | + ### `MilestoneStats` Contains statistics about a milestone. @@ -19253,11 +19279,19 @@ Implementations: - [`Iteration`](#iteration) - [`Milestone`](#milestone) -##### Fields +##### Fields with arguments + +###### `TimeboxReportInterface.report` + +Historically accurate report about the timebox. + +Returns [`TimeboxReport`](#timeboxreport). + +####### Arguments | Name | Type | Description | | ---- | ---- | ----------- | -| <a id="timeboxreportinterfacereport"></a>`report` | [`TimeboxReport`](#timeboxreport) | Historically accurate report about the timebox. | +| <a id="timeboxreportinterfacereportfullpath"></a>`fullPath` | [`String`](#string) | Full path of the project or group used as a scope for report. For example, `gitlab-org` or `gitlab-org/gitlab`. | #### `User` diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md index 74ae9bb1998..9d812ef72cd 100644 --- a/doc/integration/gitlab.md +++ b/doc/integration/gitlab.md @@ -62,9 +62,7 @@ GitLab.com generates an application ID and secret key for you to use. # label: "Provider name", # optional label for login button, defaults to "GitLab.com" app_id: "YOUR_APP_ID", app_secret: "YOUR_APP_SECRET", - args: { scope: "read_user" # optional: defaults to the scopes of the application - , client_options: { site: "https://gitlab.example.com/api/v4" } - } + args: { scope: "read_user" } # optional: defaults to the scopes of the application } ] ``` @@ -91,7 +89,6 @@ GitLab.com generates an application ID and secret key for you to use. # label: 'Provider name', # optional label for login button, defaults to "GitLab.com" app_id: 'YOUR_APP_ID', app_secret: 'YOUR_APP_SECRET', - args: { "client_options": { "site": 'https://gitlab.example.com/api/v4' } } ``` Or, for installations from source to authenticate against a different GitLab instance: diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index a807675c5c0..9e08ce9dd88 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -82,6 +82,7 @@ threads. Some quick actions might not be available to all subscription tiers. | `/parent_epic <epic>` | **{dotted-circle}** No | **{dotted-circle}** No | **{check-circle}** Yes | Set parent epic to `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic ([introduced in GitLab 12.1](https://gitlab.com/gitlab-org/gitlab/-/issues/10556)). | | `/promote` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to epic. | | `/promote_to_incident` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Promote issue to incident ([introduced in GitLab 14.5](https://gitlab.com/gitlab-org/gitlab/-/issues/296787)). | +| `/page <policy name>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Start escalations for the incident ([introduced in GitLab 14.9](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79977)). | `/publish` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Publish issue to an associated [Status Page](../../operations/incident_management/status_page.md) ([Introduced in GitLab 13.0](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30906)) | | `/reassign @user1 @user2` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Replace current assignees with those specified. | | `/reassign_reviewer @user1 @user2` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Replace current reviewers with those specified. | diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml index 93cd75ce5a7..2bc9a58ff8a 100644 --- a/lib/gitlab/database/gitlab_schemas.yml +++ b/lib/gitlab/database/gitlab_schemas.yml @@ -8,6 +8,7 @@ alert_management_alert_metric_images: :gitlab_main alert_management_alert_user_mentions: :gitlab_main alert_management_http_integrations: :gitlab_main allowed_email_domains: :gitlab_main +analytics_cycle_analytics_aggregations: :gitlab_main analytics_cycle_analytics_group_stages: :gitlab_main analytics_cycle_analytics_group_value_streams: :gitlab_main analytics_cycle_analytics_issue_stage_events: :gitlab_main diff --git a/lib/gitlab/usage_data_counters/known_events/quickactions.yml b/lib/gitlab/usage_data_counters/known_events/quickactions.yml index 49891080b03..4ba7ea2d407 100644 --- a/lib/gitlab/usage_data_counters/known_events/quickactions.yml +++ b/lib/gitlab/usage_data_counters/known_events/quickactions.yml @@ -127,6 +127,10 @@ category: quickactions redis_slot: quickactions aggregation: weekly +- name: i_quickactions_page + category: quickactions + redis_slot: quickactions + aggregation: weekly - name: i_quickactions_publish category: quickactions redis_slot: quickactions diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f97cd5d61a8..0e258a7c86b 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -14434,6 +14434,9 @@ msgstr "" msgid "Errors:" msgstr "" +msgid "Escalate this incident" +msgstr "" + msgid "Escalation Policies" msgstr "" @@ -21933,11 +21936,6 @@ msgstr "" msgid "Limit the size of Sidekiq jobs stored in Redis." msgstr "" -msgid "Limited to showing %d event at most" -msgid_plural "Limited to showing %d events at most" -msgstr[0] "" -msgstr[1] "" - msgid "Limiting mode" msgstr "" @@ -27481,6 +27479,9 @@ msgstr "" msgid "Policies" msgstr "" +msgid "Policy '%{escalation_policy_name}' does not exist." +msgstr "" + msgid "Policy management project does have any policies in %{policy_path}" msgstr "" @@ -33827,11 +33828,6 @@ msgstr "" msgid "Show whitespace changes" msgstr "" -msgid "Showing %d event" -msgid_plural "Showing %d events" -msgstr[0] "" -msgstr[1] "" - msgid "Showing %{conflict} between %{sourceBranch} and %{targetBranch}" msgstr "" @@ -34806,6 +34802,9 @@ msgstr "" msgid "Started asynchronous removal of all repository check states." msgstr "" +msgid "Started escalation for this incident." +msgstr "" + msgid "Starting..." msgstr "" @@ -34818,6 +34817,9 @@ msgstr "" msgid "Starts at (UTC)" msgstr "" +msgid "Starts escalations for this incident" +msgstr "" + msgid "Starts on" msgstr "" @@ -37375,6 +37377,9 @@ msgstr "" msgid "This group, its subgroups and projects will be removed on %{date} since its parent group '%{parent_group_name}' has been scheduled for removal." msgstr "" +msgid "This incident is already escalated with '%{escalation_policy_name}'." +msgstr "" + msgid "This invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}." msgstr "" diff --git a/package.json b/package.json index d05fca48850..1148763ed52 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", "@gitlab/svgs": "2.5.0", - "@gitlab/ui": "36.6.0", + "@gitlab/ui": "36.7.0", "@gitlab/visual-review-tools": "1.6.1", "@rails/actioncable": "6.1.4-6", "@rails/ujs": "6.1.4-6", diff --git a/qa/qa/support/influxdb_tools.rb b/qa/qa/support/influxdb_tools.rb index 21a77ec0e52..e53b843ca87 100644 --- a/qa/qa/support/influxdb_tools.rb +++ b/qa/qa/support/influxdb_tools.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require "active_support/core_ext/module/delegation" + module QA module Support # Common tools for use with influxdb metrics setup diff --git a/qa/qa/tools/reliable_report.rb b/qa/qa/tools/reliable_report.rb index 2735881c5d6..96e5690ce30 100644 --- a/qa/qa/tools/reliable_report.rb +++ b/qa/qa/tools/reliable_report.rb @@ -56,7 +56,7 @@ module QA { title: "Reliable e2e test report", description: report_issue_body, - labels: "Quality,test,type::maintenance,reliable test report,automation:devops-mapping-disable" + labels: "Quality,test,type::maintenance,reliable test report,automation:ml" }, headers: { "PRIVATE-TOKEN" => gitlab_access_token } ) diff --git a/qa/spec/tools/reliable_report_spec.rb b/qa/spec/tools/reliable_report_spec.rb index 85b2590d3aa..318b0833f62 100644 --- a/qa/spec/tools/reliable_report_spec.rb +++ b/qa/spec/tools/reliable_report_spec.rb @@ -167,7 +167,7 @@ describe QA::Tools::ReliableReport do payload: { title: "Reliable e2e test report", description: issue_body, - labels: "Quality,test,type::maintenance,reliable test report,automation:devops-mapping-disable" + labels: "Quality,test,type::maintenance,reliable test report,automation:ml" } ) expect(slack_notifier).to have_received(:post).with( diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 2608a13a399..a223c516dce 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -18,6 +18,7 @@ RSpec.describe 'Database schema' do approvals: %w[user_id], approver_groups: %w[target_id], approvers: %w[target_id user_id], + analytics_cycle_analytics_aggregations: %w[last_full_run_issues_id last_full_run_merge_requests_id last_incremental_issues_id last_incremental_merge_requests_id], analytics_cycle_analytics_merge_request_stage_events: %w[author_id group_id merge_request_id milestone_id project_id stage_event_hash_id state_id], analytics_cycle_analytics_issue_stage_events: %w[author_id group_id issue_id milestone_id project_id stage_event_hash_id state_id], audit_events: %w[author_id entity_id target_id], diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb index 70ee4bd3c5a..f3bd9eafbc3 100644 --- a/spec/experiments/application_experiment_spec.rb +++ b/spec/experiments/application_experiment_spec.rb @@ -18,10 +18,13 @@ RSpec.describe ApplicationExperiment, :experiment do allow(application_experiment).to receive(:enabled?).and_return(true) end - it "doesn't raise an exception without a defined control" do - # because we have a default behavior defined + it "registers a default control behavior for anonymous experiments" do + # This default control behavior is not inherited, intentionally, but it + # does provide anonymous experiments with a base control behavior to keep + # them optional there. - expect { experiment('namespaced/stub') { } }.not_to raise_error + expect(experiment(:example)).to register_behavior(:control).with(nil) + expect { experiment(:example) { } }.not_to raise_error end describe "#publish" do diff --git a/spec/features/merge_request/user_sees_pipelines_spec.rb b/spec/features/merge_request/user_sees_pipelines_spec.rb index 266ae0d8c37..de960e36d50 100644 --- a/spec/features/merge_request/user_sees_pipelines_spec.rb +++ b/spec/features/merge_request/user_sees_pipelines_spec.rb @@ -232,7 +232,7 @@ RSpec.describe 'Merge request > User sees pipelines', :js do sign_in user end - context 'when pipeline and merge request were created simultaneously' do + context 'when pipeline and merge request were created simultaneously', :delete do before do stub_ci_pipeline_to_return_yaml_file diff --git a/spec/frontend/cycle_analytics/limit_warning_component_spec.js b/spec/frontend/cycle_analytics/limit_warning_component_spec.js deleted file mode 100644 index 3dac7438909..00000000000 --- a/spec/frontend/cycle_analytics/limit_warning_component_spec.js +++ /dev/null @@ -1,41 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import LimitWarningComponent from '~/cycle_analytics/components/limit_warning_component.vue'; -import Translate from '~/vue_shared/translate'; - -Vue.use(Translate); - -const createComponent = (props) => - shallowMount(LimitWarningComponent, { - propsData: { - ...props, - }, - }); - -describe('Limit warning component', () => { - let component; - - beforeEach(() => { - component = null; - }); - - afterEach(() => { - component.destroy(); - }); - - it('should not render if count is not exactly than 50', () => { - component = createComponent({ count: 5 }); - - expect(component.text().trim()).toBe(''); - - component = createComponent({ count: 55 }); - - expect(component.text().trim()).toBe(''); - }); - - it('should render if count is exactly 50', () => { - component = createComponent({ count: 50 }); - - expect(component.text().trim()).toBe('Showing 50 events'); - }); -}); diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js index 995f0cf7ba1..cc64c7050de 100644 --- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js +++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js @@ -317,7 +317,7 @@ describe('AdminRunnersApp', () => { }); it('more pages can be selected', () => { - expect(findRunnerPagination().text()).toMatchInterpolatedText('Prev Next'); + expect(findRunnerPagination().text()).toMatchInterpolatedText('Previous Next'); }); it('cannot navigate to the previous page', () => { diff --git a/spec/frontend/runner/components/runner_pagination_spec.js b/spec/frontend/runner/components/runner_pagination_spec.js index ecd6e6bd7f9..40dac5b1b6a 100644 --- a/spec/frontend/runner/components/runner_pagination_spec.js +++ b/spec/frontend/runner/components/runner_pagination_spec.js @@ -46,7 +46,7 @@ describe('RunnerPagination', () => { }); it('Shows prev page disabled', () => { - expect(findPagination().find('[aria-disabled]').text()).toBe('Prev'); + expect(findPagination().find('[aria-disabled]').text()).toBe('Previous'); }); it('Shows next page link', () => { @@ -84,7 +84,7 @@ describe('RunnerPagination', () => { const links = findPagination().findAll('a'); expect(links).toHaveLength(2); - expect(links.at(0).text()).toBe('Prev'); + expect(links.at(0).text()).toBe('Previous'); expect(links.at(1).text()).toBe('Next'); }); @@ -125,8 +125,8 @@ describe('RunnerPagination', () => { expect(findPagination().props('nextPage')).toBe(null); }); - it('Shows next page link', () => { - expect(findPagination().find('a').text()).toBe('Prev'); + it('Shows previous page link', () => { + expect(findPagination().find('a').text()).toBe('Previous'); }); it('Shows next page disabled', () => { diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js index 7cb1f49d4f7..164e1315d4a 100644 --- a/spec/frontend/runner/group_runners/group_runners_app_spec.js +++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js @@ -304,7 +304,7 @@ describe('GroupRunnersApp', () => { }); it('more pages can be selected', () => { - expect(findRunnerPagination().text()).toMatchInterpolatedText('Prev Next'); + expect(findRunnerPagination().text()).toMatchInterpolatedText('Previous Next'); }); it('cannot navigate to the previous page', () => { diff --git a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js index 36050a42da7..8270ff31574 100644 --- a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js +++ b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js @@ -221,7 +221,7 @@ describe('AlertManagementEmptyState', () => { findPagination().vm.$emit('input', 3); await nextTick(); - expect(findPagination().findAll('.page-item').at(0).text()).toBe('Prev'); + expect(findPagination().findAll('.page-item').at(0).text()).toBe('Previous'); }); it('returns prevPage number', async () => { diff --git a/spec/migrations/backfill_cycle_analytics_aggregations_spec.rb b/spec/migrations/backfill_cycle_analytics_aggregations_spec.rb new file mode 100644 index 00000000000..2a5d33742ce --- /dev/null +++ b/spec/migrations/backfill_cycle_analytics_aggregations_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe BackfillCycleAnalyticsAggregations, :migration do + let(:migration) { described_class.new } + + let(:aggregations) { table(:analytics_cycle_analytics_aggregations) } + let(:namespaces) { table(:namespaces) } + let(:group_value_streams) { table(:analytics_cycle_analytics_group_value_streams) } + + context 'when there are value stream records' do + it 'inserts a record for each top-level namespace' do + group1 = namespaces.create!(path: 'aaa', name: 'aaa') + subgroup1 = namespaces.create!(path: 'bbb', name: 'bbb', parent_id: group1.id) + group2 = namespaces.create!(path: 'ccc', name: 'ccc') + + namespaces.create!(path: 'ddd', name: 'ddd') # not used + + group_value_streams.create!(name: 'for top level group', group_id: group2.id) + group_value_streams.create!(name: 'another for top level group', group_id: group2.id) + + group_value_streams.create!(name: 'for subgroup', group_id: subgroup1.id) + group_value_streams.create!(name: 'another for subgroup', group_id: subgroup1.id) + + migrate! + + expect(aggregations.pluck(:group_id)).to match_array([group1.id, group2.id]) + end + end + + it 'does nothing' do + expect { migrate! }.not_to change { aggregations.count } + end +end diff --git a/spec/models/analytics/cycle_analytics/aggregation_spec.rb b/spec/models/analytics/cycle_analytics/aggregation_spec.rb new file mode 100644 index 00000000000..4ccebd6bf2e --- /dev/null +++ b/spec/models/analytics/cycle_analytics/aggregation_spec.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model do + describe 'associations' do + it { is_expected.to belong_to(:group).required } + end + + describe 'validations' do + it { is_expected.not_to validate_presence_of(:group) } + it { is_expected.not_to validate_presence_of(:enabled) } + + %i[incremental_runtimes_in_seconds incremental_processed_records last_full_run_runtimes_in_seconds last_full_run_processed_records].each do |column| + it "validates the array length of #{column}" do + record = described_class.new(column => [1] * 11) + + expect(record).to be_invalid + expect(record.errors).to have_key(column) + end + end + end + + describe '#safe_create_for_group' do + let_it_be(:group) { create(:group) } + let_it_be(:subgroup) { create(:group, parent: group) } + + it 'creates the aggregation record' do + described_class.safe_create_for_group(group) + + record = described_class.find_by(group_id: group) + expect(record).to be_present + end + + context 'when non top-level group is given' do + it 'creates the aggregation record for the top-level group' do + described_class.safe_create_for_group(subgroup) + + record = described_class.find_by(group_id: group) + expect(record).to be_present + end + end + + context 'when the record is already present' do + it 'does nothing' do + described_class.safe_create_for_group(group) + + expect do + described_class.safe_create_for_group(group) + described_class.safe_create_for_group(subgroup) + end.not_to change { described_class.count } + end + end + end +end diff --git a/spec/support/shared_examples/models/resource_event_shared_examples.rb b/spec/support/shared_examples/models/resource_event_shared_examples.rb index c0158f9b24b..80806ee768a 100644 --- a/spec/support/shared_examples/models/resource_event_shared_examples.rb +++ b/spec/support/shared_examples/models/resource_event_shared_examples.rb @@ -62,15 +62,15 @@ RSpec.shared_examples 'a resource event for issues' do let_it_be(:issue2) { create(:issue, author: user1) } let_it_be(:issue3) { create(:issue, author: user2) } + let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue1) } + let_it_be(:event2) { create(described_class.name.underscore.to_sym, issue: issue2) } + let_it_be(:event3) { create(described_class.name.underscore.to_sym, issue: issue1) } + describe 'associations' do it { is_expected.to belong_to(:issue) } end describe '.by_issue' do - let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue1) } - let_it_be(:event2) { create(described_class.name.underscore.to_sym, issue: issue2) } - let_it_be(:event3) { create(described_class.name.underscore.to_sym, issue: issue1) } - it 'returns the expected records for an issue with events' do events = described_class.by_issue(issue1) @@ -84,21 +84,29 @@ RSpec.shared_examples 'a resource event for issues' do end end - describe '.by_issue_ids_and_created_at_earlier_or_equal_to' do + describe '.by_issue_ids' do + it 'returns the expected events' do + events = described_class.by_issue_ids([issue1.id]) + + expect(events).to contain_exactly(event1, event3) + end + end + + describe '.by_created_at_earlier_or_equal_to' do let_it_be(:event1) { create(described_class.name.underscore.to_sym, issue: issue1, created_at: '2020-03-10') } let_it_be(:event2) { create(described_class.name.underscore.to_sym, issue: issue2, created_at: '2020-03-10') } let_it_be(:event3) { create(described_class.name.underscore.to_sym, issue: issue1, created_at: '2020-03-12') } - it 'returns the expected records for an issue with events' do - events = described_class.by_issue_ids_and_created_at_earlier_or_equal_to([issue1.id, issue2.id], '2020-03-11 23:59:59') + it 'returns the expected events' do + events = described_class.by_created_at_earlier_or_equal_to('2020-03-11 23:59:59') expect(events).to contain_exactly(event1, event2) end - it 'returns the expected records for an issue with no events' do - events = described_class.by_issue_ids_and_created_at_earlier_or_equal_to(issue3, '2020-03-12') + it 'returns the expected events' do + events = described_class.by_created_at_earlier_or_equal_to('2020-03-12') - expect(events).to be_empty + expect(events).to contain_exactly(event1, event2, event3) end end diff --git a/yarn.lock b/yarn.lock index f36a1712029..513a0e841ac 100644 --- a/yarn.lock +++ b/yarn.lock @@ -986,10 +986,10 @@ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-2.5.0.tgz#e0569916fa858462b1801cc90ef8dd9706a12e96" integrity sha512-cH/EBs//wdkH6kG+kDpvRCIl63/A8JgjAhBJ+ZWucPgtNCDD6x6RDMGdQrxSqhYwcCKDoLStfcxmblBkuiSRXQ== -"@gitlab/ui@36.6.0": - version "36.6.0" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-36.6.0.tgz#902ec76623de3b46d450fbe2074d00a39a58d61c" - integrity sha512-hHuknkt4KTQVPEA8t+Cg29hocqMUv4bYfVH7Hinj3qFaIK32zMKUGQ2P/w5BG8R+cP9PTjw+WxNYc4WpRPpcUw== +"@gitlab/ui@36.7.0": + version "36.7.0" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-36.7.0.tgz#0b4c5709bf0a9be9a24516a32181526b8367777d" + integrity sha512-IVY6uz5ICozLVJS7t0Pd70qyzMzNNiEULlKVcMoYEEkQQ0Iu/qKj016e5Xm8i9wheSqG2l/fMzUxuNbtftGu2w== dependencies: "@babel/standalone" "^7.0.0" bootstrap-vue "2.20.1" |