Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-12-16 03:10:37 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-12-16 03:10:37 +0300
commit84025108bca604c1428d2cf6a6b69616ee90956c (patch)
tree7c0fd8ec032c12bee563daeee6c721e2d7f7d44f /spec
parent73778b9c53d13a2e06a693c30073366deedead8f (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/fixtures/scripts/internal_events/events/ee_event_without_identifiers.yml14
-rw-r--r--spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml20
-rw-r--r--spec/fixtures/scripts/internal_events/events/keyboard_smashed_event.yml20
-rw-r--r--spec/fixtures/scripts/internal_events/events/secondary_event_with_identifiers.yml20
-rw-r--r--spec/fixtures/scripts/internal_events/metrics/ee_total_28d_single_event.yml25
-rw-r--r--spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event.yml25
-rw-r--r--spec/fixtures/scripts/internal_events/metrics/ee_total_single_event.yml25
-rw-r--r--spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_28d.yml28
-rw-r--r--spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_7d.yml28
-rw-r--r--spec/fixtures/scripts/internal_events/metrics/project_id_28d_multiple_events.yml31
-rw-r--r--spec/fixtures/scripts/internal_events/metrics/project_id_7d_multiple_events.yml31
-rw-r--r--spec/fixtures/scripts/internal_events/metrics/total_single_event.yml27
-rw-r--r--spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml28
-rw-r--r--spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml28
-rw-r--r--spec/fixtures/scripts/internal_events/new_events.yml183
-rw-r--r--spec/fixtures/scripts/internal_events/new_metrics.yml196
-rw-r--r--spec/fixtures/scripts/internal_events/stages.yml78
-rw-r--r--spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js30
-rw-r--r--spec/frontend/admin/abuse_report/mock_data.js12
-rw-r--r--spec/frontend/deploy_keys/graphql/resolvers_spec.js249
-rw-r--r--spec/frontend/environments/graphql/resolvers/base_spec.js2
-rw-r--r--spec/frontend/security_configuration/components/app_spec.js6
-rw-r--r--spec/frontend/security_configuration/components/feature_card_spec.js2
-rw-r--r--spec/frontend/security_configuration/components/training_provider_list_spec.js7
-rw-r--r--spec/frontend/security_configuration/mock_data.js2
-rw-r--r--spec/frontend/security_configuration/utils_spec.js2
-rw-r--r--spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js2
-rw-r--r--spec/graphql/types/permission_types/abuse_report_spec.rb15
-rw-r--r--spec/helpers/groups_helper_spec.rb35
-rw-r--r--spec/policies/abuse_report_policy_spec.rb2
-rw-r--r--spec/requests/api/graphql/abuse_report_spec.rb6
-rw-r--r--spec/scripts/internal_events/cli_spec.rb866
-rw-r--r--spec/serializers/merge_request_poll_widget_entity_spec.rb3
33 files changed, 2005 insertions, 43 deletions
diff --git a/spec/fixtures/scripts/internal_events/events/ee_event_without_identifiers.yml b/spec/fixtures/scripts/internal_events/events/ee_event_without_identifiers.yml
new file mode 100644
index 00000000000..07f606fbe33
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/events/ee_event_without_identifiers.yml
@@ -0,0 +1,14 @@
+---
+description: Internal Event CLI is opened
+category: InternalEventTracking
+action: internal_events_cli_opened
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+milestone: '16.6'
+introduced_by_url: TODO
+distributions:
+- ee
+tiers:
+- premium
+- ultimate
diff --git a/spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml b/spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
new file mode 100644
index 00000000000..5050953920d
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
@@ -0,0 +1,20 @@
+---
+description: Engineer uses Internal Event CLI to define a new event
+category: InternalEventTracking
+action: internal_events_cli_used
+identifiers:
+- project
+- namespace
+- user
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+milestone: '16.6'
+introduced_by_url: TODO
+distributions:
+- ce
+- ee
+tiers:
+- free
+- premium
+- ultimate
diff --git a/spec/fixtures/scripts/internal_events/events/keyboard_smashed_event.yml b/spec/fixtures/scripts/internal_events/events/keyboard_smashed_event.yml
new file mode 100644
index 00000000000..c0ccbc03af7
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/events/keyboard_smashed_event.yml
@@ -0,0 +1,20 @@
+---
+description: random event string
+category: InternalEventTracking
+action: random_name
+identifiers:
+- project
+- namespace
+- user
+product_section: core_platform
+product_stage: manage
+product_group: import_and_integrate
+milestone: '16.6'
+introduced_by_url: TODO
+distributions:
+- ce
+- ee
+tiers:
+- free
+- premium
+- ultimate
diff --git a/spec/fixtures/scripts/internal_events/events/secondary_event_with_identifiers.yml b/spec/fixtures/scripts/internal_events/events/secondary_event_with_identifiers.yml
new file mode 100644
index 00000000000..4e2e77e0c5c
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/events/secondary_event_with_identifiers.yml
@@ -0,0 +1,20 @@
+---
+description: Engineer closes Internal Event CLI
+category: InternalEventTracking
+action: internal_events_cli_closed
+identifiers:
+- project
+- namespace
+- user
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+milestone: '16.6'
+introduced_by_url: TODO
+distributions:
+- ce
+- ee
+tiers:
+- free
+- premium
+- ultimate
diff --git a/spec/fixtures/scripts/internal_events/metrics/ee_total_28d_single_event.yml b/spec/fixtures/scripts/internal_events/metrics/ee_total_28d_single_event.yml
new file mode 100644
index 00000000000..ba56d782871
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/metrics/ee_total_28d_single_event.yml
@@ -0,0 +1,25 @@
+---
+key_path: counts.count_total_internal_events_cli_used_monthly
+description: Monthly count of when an event was defined using the CLI
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: '16.6'
+introduced_by_url: TODO
+time_frame: 28d
+data_source: internal_events
+data_category: optional
+instrumentation_class: TotalCountMetric
+distribution:
+- ee
+tier:
+- premium
+- ultimate
+options:
+ events:
+ - internal_events_cli_used
+events:
+- name: internal_events_cli_used
diff --git a/spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event.yml b/spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event.yml
new file mode 100644
index 00000000000..e6bdcb9d2ae
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event.yml
@@ -0,0 +1,25 @@
+---
+key_path: counts.count_total_internal_events_cli_used_weekly
+description: Weekly count of when an event was defined using the CLI
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: '16.6'
+introduced_by_url: TODO
+time_frame: 7d
+data_source: internal_events
+data_category: optional
+instrumentation_class: TotalCountMetric
+distribution:
+- ee
+tier:
+- premium
+- ultimate
+options:
+ events:
+ - internal_events_cli_used
+events:
+- name: internal_events_cli_used
diff --git a/spec/fixtures/scripts/internal_events/metrics/ee_total_single_event.yml b/spec/fixtures/scripts/internal_events/metrics/ee_total_single_event.yml
new file mode 100644
index 00000000000..b1bf89dc095
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/metrics/ee_total_single_event.yml
@@ -0,0 +1,25 @@
+---
+key_path: counts.count_total_internal_events_cli_used
+description: Total count of when an event was defined using the CLI
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: '16.6'
+introduced_by_url: TODO
+time_frame: all
+data_source: internal_events
+data_category: optional
+instrumentation_class: TotalCountMetric
+distribution:
+- ee
+tier:
+- premium
+- ultimate
+options:
+ events:
+ - internal_events_cli_used
+events:
+- name: internal_events_cli_used
diff --git a/spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_28d.yml b/spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_28d.yml
new file mode 100644
index 00000000000..8476cb8561b
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_28d.yml
@@ -0,0 +1,28 @@
+---
+key_path: redis_hll_counters.count_distinct_user_id_from_random_name_monthly
+description: Monthly count of unique users random metric string
+product_section: core_platform
+product_stage: manage
+product_group: import_and_integrate
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: '16.6'
+introduced_by_url: TODO
+time_frame: 28d
+data_source: internal_events
+data_category: optional
+instrumentation_class: RedisHLLMetric
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - random_name
+events:
+- name: random_name
+ unique: user.id
diff --git a/spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_7d.yml b/spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_7d.yml
new file mode 100644
index 00000000000..b4cc2fc8b55
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_7d.yml
@@ -0,0 +1,28 @@
+---
+key_path: redis_hll_counters.count_distinct_user_id_from_random_name_weekly
+description: Weekly count of unique users random metric string
+product_section: core_platform
+product_stage: manage
+product_group: import_and_integrate
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: '16.6'
+introduced_by_url: TODO
+time_frame: 7d
+data_source: internal_events
+data_category: optional
+instrumentation_class: RedisHLLMetric
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - random_name
+events:
+- name: random_name
+ unique: user.id
diff --git a/spec/fixtures/scripts/internal_events/metrics/project_id_28d_multiple_events.yml b/spec/fixtures/scripts/internal_events/metrics/project_id_28d_multiple_events.yml
new file mode 100644
index 00000000000..754702c8c74
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/metrics/project_id_28d_multiple_events.yml
@@ -0,0 +1,31 @@
+---
+key_path: redis_hll_counters.count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_monthly
+description: Monthly count of unique projects where a defition file was created with the CLI
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: '16.6'
+introduced_by_url: TODO
+time_frame: 28d
+data_source: internal_events
+data_category: optional
+instrumentation_class: RedisHLLMetric
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - internal_events_cli_closed
+ - internal_events_cli_used
+events:
+- name: internal_events_cli_closed
+ unique: project.id
+- name: internal_events_cli_used
+ unique: project.id
diff --git a/spec/fixtures/scripts/internal_events/metrics/project_id_7d_multiple_events.yml b/spec/fixtures/scripts/internal_events/metrics/project_id_7d_multiple_events.yml
new file mode 100644
index 00000000000..95f429e9b40
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/metrics/project_id_7d_multiple_events.yml
@@ -0,0 +1,31 @@
+---
+key_path: redis_hll_counters.count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_weekly
+description: Weekly count of unique projects where a defition file was created with the CLI
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: '16.6'
+introduced_by_url: TODO
+time_frame: 7d
+data_source: internal_events
+data_category: optional
+instrumentation_class: RedisHLLMetric
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - internal_events_cli_closed
+ - internal_events_cli_used
+events:
+- name: internal_events_cli_closed
+ unique: project.id
+- name: internal_events_cli_used
+ unique: project.id
diff --git a/spec/fixtures/scripts/internal_events/metrics/total_single_event.yml b/spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
new file mode 100644
index 00000000000..5bdb4c45a52
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
@@ -0,0 +1,27 @@
+---
+key_path: counts.count_total_internal_events_cli_used
+description: Total count of when an event was defined using the CLI
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: '16.6'
+introduced_by_url: TODO
+time_frame: all
+data_source: internal_events
+data_category: optional
+instrumentation_class: TotalCountMetric
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - internal_events_cli_used
+events:
+- name: internal_events_cli_used
diff --git a/spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml b/spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml
new file mode 100644
index 00000000000..b176b23b46a
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml
@@ -0,0 +1,28 @@
+---
+key_path: redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used_monthly
+description: Monthly count of unique users who defined an internal event using the CLI
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: '16.6'
+introduced_by_url: TODO
+time_frame: 28d
+data_source: internal_events
+data_category: optional
+instrumentation_class: RedisHLLMetric
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - internal_events_cli_used
+events:
+- name: internal_events_cli_used
+ unique: user.id
diff --git a/spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml b/spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml
new file mode 100644
index 00000000000..8a0fca2cbdc
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml
@@ -0,0 +1,28 @@
+---
+key_path: redis_hll_counters.count_distinct_user_id_from_internal_events_cli_used_weekly
+description: Weekly count of unique users who defined an internal event using the CLI
+product_section: analytics
+product_stage: monitor
+product_group: analytics_instrumentation
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: '16.6'
+introduced_by_url: TODO
+time_frame: 7d
+data_source: internal_events
+data_category: optional
+instrumentation_class: RedisHLLMetric
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - internal_events_cli_used
+events:
+- name: internal_events_cli_used
+ unique: user.id
diff --git a/spec/fixtures/scripts/internal_events/new_events.yml b/spec/fixtures/scripts/internal_events/new_events.yml
new file mode 100644
index 00000000000..6f39fc5e93c
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/new_events.yml
@@ -0,0 +1,183 @@
+- description: Creates a new event and flows directly into metric creation
+ inputs:
+ keystrokes:
+ - "1\n" # Enum-select: New Event -- start tracking when an action or scenario occurs on gitlab instances
+ - "Engineer uses Internal Event CLI to define a new event\n" # Submit description
+ - "internal_events_cli_used\n" # Submit action name
+ - "1\n" # Select: [namespace, project, user]
+ - "\n" # Skip MR URL
+ - "instrumentation" # Filters to the analytics instrumentation group
+ - "\n" # Accept analytics:monitor:analytics_instrumentation
+ - "1\n" # Select: [free, premium, ultimate]
+ - "y\n" # Create file
+ - "1\n" # Select: Create Metric --- define a new metric
+ - "\e[A" # Arrow up to: Total count of events
+ - "\n" # Select: Total count of events
+ - "when an event was defined using the CLI\n" # Input description
+ - "1\n" # Select: Copy & continue
+ - "y\n" # Create file
+ - "2\n" # Exit
+ outputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ - path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
+
+- description: Requires description & action before continuing
+ inputs:
+ keystrokes:
+ - "1\n" # Enum-select: New Event -- start tracking when an action or scenario occurs on gitlab instances
+ - "\n" # Attempt to skip writing description --> should get help message
+ - "Engineer uses Internal Event CLI to define a new event\n" # Submit description
+ - "\n" # Attempt to skip naming action --> should get help message
+ - "internal_events_cli_used\n" # Submit action name
+ - "1\n" # Select [namespace, project, user]
+ - "\n" # Skip MR URL
+ - "instrumentation" # Filters to the analytics instrumentation group
+ - "\n" # Accept analytics:monitor:analytics_instrumentation
+ - "1\n" # Select [free, premium, ultimate]
+ - "y\n" # Create file
+ - "3\n" # Exit
+ outputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+
+- description: Does not allow existing events for action
+ inputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ keystrokes:
+ - "1\n" # Enum-select: New Event -- start tracking when an action or scenario occurs on gitlab instances
+ - "Engineer closes Internal Event CLI\n" # Submit description
+ - "internal_events_cli_used\n" # Submit already-existing action name
+ - "internal_events_cli_closed\n" # Submit alterred action name
+ - "1\n" # Select [namespace, project, user]
+ - "\n" # Skip MR URL
+ - "instrumentation" # Filters to the analytics instrumentation group
+ - "\n" # Accept analytics:monitor:analytics_instrumentation
+ - "1\n" # Select [free, premium, ultimate]
+ - "y\n" # Create file
+ - "3\n" # Exit
+ outputs:
+ files:
+ - path: config/events/internal_events_cli_closed.yml
+ content: spec/fixtures/scripts/internal_events/events/secondary_event_with_identifiers.yml
+
+- description: Creates a new event without identifiers
+ inputs:
+ keystrokes:
+ - "1\n" # Enum-select: New Event -- start tracking when an action or scenario occurs on gitlab instances
+ - "Internal Event CLI is opened\n" # Submit description
+ - "internal_events_cli_opened\n" # Submit action name
+ - "6\n" # Select: None
+ - "\n" # Skip MR URL
+ - "instrumentation" # Filters to the analytics instrumentation group
+ - "\n" # Accept analytics:monitor:analytics_instrumentation
+ - "2\n" # Select [premium, ultimate]
+ - "y\n" # Create file
+ - "3\n" # Exit
+ outputs:
+ files:
+ - path: ee/config/events/internal_events_cli_opened.yml
+ content: spec/fixtures/scripts/internal_events/events/ee_event_without_identifiers.yml
+
+- description: Smashing the keyboard/return creates an event & metrics with the most common attributes, then shows usage
+ inputs:
+ keystrokes:
+ - "\n" # Enum-select: New Event -- start tracking when an action or scenario occurs on gitlab instances
+ - "random event string\n" # Submit keyboard-smashing description
+ - "random_name\n" # Submit keyboard-smashing action name
+ - "\n" # Select: [namespace, project, user]
+ - "\n" # Skip MR URL
+ - "\n" # Select core_platform:manage:import_and_integrate
+ - "\n" # Select [free, premium, ultimate]
+ - "\n" # Create file
+ - "\n" # Select: Create Metric --- define a new metric
+ - "\n" # Select: Weekly/Monthly count of unique users
+ - "random metric string\n" # Submit keyboard-smashing description
+ - "\n" # Accept weekly description for monthly
+ - "\n" # Select: Copy & continue
+ - "\n" # Skip URL
+ - "\n" # Create file
+ - "\n" # Create file
+ - "\n" # Select: View Usage -- look at code examples
+ - "\n" # Select: Ruby/Rails
+ - "8\n" # Exit
+ outputs:
+ files:
+ - path: config/events/random_name.yml
+ content: spec/fixtures/scripts/internal_events/events/keyboard_smashed_event.yml
+ - path: config/metrics/counts_28d/count_distinct_user_id_from_random_name_monthly.yml
+ content: spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_28d.yml
+ - path: config/metrics/counts_7d/count_distinct_user_id_from_random_name_weekly.yml
+ content: spec/fixtures/scripts/internal_events/metrics/keyboard_smashed_metric_7d.yml
+
+- description: Creates an event after helping the user figure out next steps
+ inputs:
+ keystrokes:
+ - "4\n" # Enum-select: ...am I in the right place?
+ - "y\n" # Yes --> Are you trying to track customer usage of a GitLab feature?
+ - "y\n" # Yes --> Can usage for the feature be measured by tracking a specific user action?
+ - "n\n" # No --> Is the event already tracked?
+ - "y\n" # Yes --> Ready to start?
+ - "Internal Event CLI is opened\n" # Submit description
+ - "internal_events_cli_opened\n" # Submit action name
+ - "6\n" # Select: None
+ - "\n" # Skip MR URL
+ - "instrumentation" # Filters to the analytics instrumentation group
+ - "\n" # Accept analytics:monitor:analytics_instrumentation
+ - "2\n" # Select [premium, ultimate]
+ - "y\n" # Create file
+ - "3\n" # Exit
+ outputs:
+ files:
+ - path: ee/config/events/internal_events_cli_opened.yml
+ content: spec/fixtures/scripts/internal_events/events/ee_event_without_identifiers.yml
+
+- description: Creates a new event and flows directly into usage examples
+ inputs:
+ keystrokes:
+ - "1\n" # Enum-select: New Event -- start tracking when an action or scenario occurs on gitlab instances
+ - "Engineer uses Internal Event CLI to define a new event\n" # Submit description
+ - "internal_events_cli_used\n" # Submit action name
+ - "1\n" # Select: [namespace, project, user]
+ - "\n" # Skip MR URL
+ - "instrumentation" # Filters to the analytics instrumentation group
+ - "\n" # Accept analytics:monitor:analytics_instrumentation
+ - "1\n" # Select: [free, premium, ultimate]
+ - "y\n" # Create file
+ - "2\n" # Select: View Usage
+ - "8\n" # Exit
+ outputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+
+- description: Skips event creation, then saves event & flows directly into metric creation
+ inputs:
+ keystrokes:
+ - "1\n" # Enum-select: New Event -- start tracking when an action or scenario occurs on gitlab instances
+ - "Engineer uses Internal Event CLI to define a new event\n" # Submit description
+ - "internal_events_cli_used\n" # Submit action name
+ - "1\n" # Select: [namespace, project, user]
+ - "\n" # Skip MR URL
+ - "instrumentation" # Filters to the analytics instrumentation group
+ - "\n" # Accept analytics:monitor:analytics_instrumentation
+ - "1\n" # Select: [free, premium, ultimate]
+ - "n\n" # Create file
+ - "1\n" # Select: Save event & create Metric --- define a new metric
+ - "\e[A" # Arrow up to: Total count of events
+ - "\n" # Select: Total count of events
+ - "when an event was defined using the CLI\n" # Input description
+ - "1\n" # Select: Copy & continue
+ - "y\n" # Create file
+ - "2\n" # Exit
+ outputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ - path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
diff --git a/spec/fixtures/scripts/internal_events/new_metrics.yml b/spec/fixtures/scripts/internal_events/new_metrics.yml
new file mode 100644
index 00000000000..2a207ee84f4
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/new_metrics.yml
@@ -0,0 +1,196 @@
+- description: Create a weekly/monthly metric for a single event
+ inputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ keystrokes:
+ - "2\n" # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ - "1\n" # Enum-select: Single event -- count occurrences of a specific event or user interaction
+ - 'internal_events_cli_used' # Filters to this event
+ - "\n" # Select: config/events/internal_events_cli_used.yml
+ - "\n" # Select: Weekly count of unique users
+ - "who defined an internal event using the CLI\n" # Input description
+ - "\n" # Submit weekly description for monthly
+ - "1\n" # Enum-select: Copy & continue
+ - "y\n" # Create file
+ - "y\n" # Create file
+ - "2\n" # Exit
+ outputs:
+ files:
+ - path: config/metrics/counts_28d/count_distinct_user_id_from_internal_events_cli_used_monthly.yml
+ content: spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml
+ - path: config/metrics/counts_7d/count_distinct_user_id_from_internal_events_cli_used_weekly.yml
+ content: spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml
+
+- description: Create a weekly/monthly metric for a multiple events, but select only one event
+ inputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ keystrokes:
+ - "2\n" # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ - "2\n" # Enum-select: Multiple events -- count occurrences of several separate events or interactions
+ - 'internal_events_cli_used' # Filters to this event
+ - " " # Multi-select: config/events/internal_events_cli_used.yml
+ - "\n" # Submit selections
+ - "\n" # Select: Weekly count of unique projects
+ - "who defined an internal event using the CLI\n" # Input description
+ - "\n" # Submit weekly description for monthly
+ - "1\n" # Enum-select: Copy & continue
+ - "y\n" # Create file
+ - "y\n" # Create file
+ - "2\n" # Exit
+ outputs:
+ files:
+ - path: config/metrics/counts_28d/count_distinct_user_id_from_internal_events_cli_used_monthly.yml
+ content: spec/fixtures/scripts/internal_events/metrics/user_id_28d_single_event.yml
+ - path: config/metrics/counts_7d/count_distinct_user_id_from_internal_events_cli_used_weekly.yml
+ content: spec/fixtures/scripts/internal_events/metrics/user_id_7d_single_event.yml
+
+- description: Create a weekly/monthly metric for multiple events
+ inputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ - path: config/events/internal_events_cli_closed.yml
+ content: spec/fixtures/scripts/internal_events/events/secondary_event_with_identifiers.yml
+ keystrokes:
+ - "2\n" # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ - "2\n" # Enum-select: Multiple events -- count occurrences of several separate events or interactions
+ - 'internal_events_cli' # Filters to the relevant events
+ - ' ' # Multi-select: internal_events_cli_closed
+ - "\e[B" # Arrow down to: internal_events_cli_used
+ - ' ' # Multi-select: internal_events_cli_used
+ - "\n" # Submit selections
+ - "\e[B" # Arrow down to: Weekly count of unique projects
+ - "\n" # Select: Weekly count of unique projects
+ - "where a defition file was created with the CLI\n" # Input description
+ - "\n" # Submit weekly description for monthly
+ - "1\n" # Select: Copy & continue
+ - "y\n" # Create file
+ - "y\n" # Create file
+ - "2\n" # Exit
+ outputs:
+ files:
+ - path: config/metrics/counts_28d/count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_monthly.yml
+ content: spec/fixtures/scripts/internal_events/metrics/project_id_28d_multiple_events.yml
+ - path: config/metrics/counts_7d/count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_weekly.yml
+ content: spec/fixtures/scripts/internal_events/metrics/project_id_7d_multiple_events.yml
+
+- description: Create an all time total metric for a single event
+ inputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ keystrokes:
+ - "2\n" # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ - "1\n" # Enum-select: Single event -- count occurrences of a specific event or user interaction
+ - 'internal_events_cli_used' # Filters to this event
+ - "\n" # Select: config/events/internal_events_cli_used.yml
+ - "\e[A" # Arrow up to: Total count of events
+ - "\n" # Select: Total count of events
+ - "when an event was defined using the CLI\n" # Input description
+ - "1\n" # Select: Copy & continue
+ - "y\n" # Create file
+ - "2\n" # Exit
+ outputs:
+ files:
+ - path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
+
+- description: Try to create a database metric
+ inputs:
+ keystrokes:
+ - "2\n" # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ - "3\n" # Enum-select: Database -- record value of a particular field or count of database rows
+
+- description: Create an all time total metric for a single event, and confirm each attribute copied from event
+ inputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ keystrokes:
+ - "2\n" # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ - "1\n" # Enum-select: Single event -- count occurrences of a specific event or user interaction
+ - 'internal_events_cli_used' # Filters to this event
+ - "\n" # Select: config/events/internal_events_cli_used.yml
+ - "\e[A" # Arrow up to: Total count of events
+ - "\n" # Select: Total count of events
+ - "when an event was defined using the CLI\n" # Input description
+ - "2\n" # Enum-select: Modify attributes
+ - "\n" # Accept group/section/stage from event definition
+ - "\n" # Accept URL from event definition
+ - "2\n" # Override tier -> Select: [premium, ultimate]
+ - "y\n" # Create file
+ - "2\n" # Exit
+ outputs:
+ files:
+ - path: ee/config/metrics/counts_all/count_total_internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/metrics/ee_total_single_event.yml
+
+- description: Create a metric after helping the user figure out next steps
+ inputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ keystrokes:
+ - "4\n" # Enum-select: ...am I in the right place?
+ - "y\n" # Yes --> Are you trying to track customer usage of a GitLab feature?
+ - "y\n" # Yes --> Can usage for the feature be measured by tracking a specific user action?
+ - "y\n" # Yes --> Is the event already tracked?
+ - "y\n" # Yes --> Ready to start?
+ - "1\n" # Enum-select: Single event -- count occurrences of a specific event or user interaction
+ - 'internal_events_cli_used' # Filters to this event
+ - "\n" # Select: config/events/internal_events_cli_used.yml
+ - "\e[A" # Arrow up to: Total count of events
+ - "\n" # Select: Total count of events
+ - "when an event was defined using the CLI\n" # Input description
+ - "1\n" # Select: Copy & continue
+ - "y\n" # Create file
+ - "2\n" # Exit
+ outputs:
+ files:
+ - path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
+
+- description: User overwrites metric that already exists
+ inputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ - path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event.yml # wrong content
+ keystrokes:
+ - "2\n" # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ - "1\n" # Enum-select: Single event -- count occurrences of a specific event or user interaction
+ - 'internal_events_cli_used' # Filters to this event
+ - "\n" # Select: config/events/internal_events_cli_used.yml
+ - "\e[A" # Arrow up to: Total count of events
+ - "\n" # Select: Total count of events
+ - "when an event was defined using the CLI\n" # Input description
+ - "1\n" # Select: Copy & continue
+ - "y\n" # Overwrite file
+ - "2\n" # Exit
+ outputs:
+ files:
+ - path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/metrics/total_single_event.yml
+
+- description: User opts not to overwrite metric that already exists
+ inputs:
+ files:
+ - path: config/events/internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/events/event_with_identifiers.yml
+ - path: config/metrics/counts_all/count_total_internal_events_cli_used.yml
+ content: spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event.yml # wrong content
+ keystrokes:
+ - "2\n" # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ - "1\n" # Enum-select: Single event -- count occurrences of a specific event or user interaction
+ - 'internal_events_cli_used' # Filters to this event
+ - "\n" # Select: config/events/internal_events_cli_used.yml
+ - "\e[A" # Arrow up to: Total count of events
+ - "\n" # Select: Total count of events
+ - "when an event was defined using the CLI\n" # Input description
+ - "1\n" # Select: Copy & continue
+ - "n\n" # Don't overwrite file
+ - "2\n" # Exit
diff --git a/spec/fixtures/scripts/internal_events/stages.yml b/spec/fixtures/scripts/internal_events/stages.yml
new file mode 100644
index 00000000000..d5db9dcbe6d
--- /dev/null
+++ b/spec/fixtures/scripts/internal_events/stages.yml
@@ -0,0 +1,78 @@
+stages:
+ manage:
+ display_name: "Manage"
+ section: core_platform
+ groups:
+ import_and_integrate:
+ name: Import and Integrate
+ foundations:
+ name: Foundations
+
+ plan:
+ display_name: "Plan"
+ section: dev
+ groups:
+ project_management:
+ name: Project Management
+ product_planning:
+ name: Product Planning
+ knowledge:
+ name: Knowledge
+ optimize:
+ name: Optimize
+
+ create:
+ display_name: "Create"
+ section: dev
+ slack:
+ channel: s_create
+ groups:
+ source_code:
+ name: Source Code
+ code_review:
+ name: Code Review
+ ide:
+ name: IDE
+ editor_extensions:
+ name: Editor Extensions
+ code_creation:
+ name: Code Creation
+
+ verify:
+ display_name: "Verify"
+ section: ci
+ slack:
+ channel: s_verify
+ groups:
+ pipeline_execution:
+ name: "Pipeline Execution"
+ pipeline_authoring:
+ name: "Pipeline Authoring"
+ runner:
+ name: "Runner"
+ runner_saas:
+ name: "Runner SaaS"
+ pipeline_security:
+ name: "Pipeline Security"
+
+ package:
+ display_name: "Package"
+ section: ci
+ slack:
+ channel: s_package
+ groups:
+ package_registry:
+ name: Package Registry
+ container_registry:
+ name: Container Registry
+
+ monitor:
+ display_name: Monitor
+ section: analytics
+ groups:
+ analytics_instrumentation:
+ name: Analytics Instrumentation
+ product_analytics:
+ name: Product Analytics
+ observability:
+ name: "Observability"
diff --git a/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js b/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js
index f0174cd7b4a..bc7aa8ef5de 100644
--- a/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js
+++ b/spec/frontend/admin/abuse_report/components/notes/abuse_report_note_spec.js
@@ -93,6 +93,26 @@ describe('Abuse Report Note', () => {
});
describe('Editing', () => {
+ it('should show edit button when resolveNote is true', () => {
+ createComponent({
+ note: { ...mockNote, userPermissions: { resolveNote: true } },
+ });
+
+ expect(findNoteActions().props()).toMatchObject({
+ showEditButton: true,
+ });
+ });
+
+ it('should not show edit button when resolveNote is false', () => {
+ createComponent({
+ note: { ...mockNote, userPermissions: { resolveNote: false } },
+ });
+
+ expect(findNoteActions().props()).toMatchObject({
+ showEditButton: false,
+ });
+ });
+
it('should not be in edit mode by default', () => {
expect(findEditNote().exists()).toBe(false);
});
@@ -164,16 +184,14 @@ describe('Abuse Report Note', () => {
});
});
- describe('Actions', () => {
- it('should show note actions', () => {
- expect(findNoteActions().exists()).toBe(true);
+ describe('Replying', () => {
+ it('should show reply button', () => {
expect(findNoteActions().props()).toMatchObject({
- showReplyButton: mockShowReplyButton,
- showEditButton: true,
+ showReplyButton: true,
});
});
- it('should emit `startReplying`', () => {
+ it('should bubble up `startReplying` event', () => {
findNoteActions().vm.$emit('startReplying');
expect(wrapper.emitted('startReplying')).toHaveLength(1);
diff --git a/spec/frontend/admin/abuse_report/mock_data.js b/spec/frontend/admin/abuse_report/mock_data.js
index a958c4d30b1..9790b44c976 100644
--- a/spec/frontend/admin/abuse_report/mock_data.js
+++ b/spec/frontend/admin/abuse_report/mock_data.js
@@ -153,7 +153,7 @@ export const mockDiscussionWithNoReplies = [
},
lastEditedBy: null,
userPermissions: {
- adminNote: true,
+ resolveNote: true,
__typename: 'NotePermissions',
},
discussion: {
@@ -192,7 +192,7 @@ export const mockDiscussionWithReplies = [
},
lastEditedBy: null,
userPermissions: {
- adminNote: true,
+ resolveNote: true,
__typename: 'NotePermissions',
},
discussion: {
@@ -237,7 +237,7 @@ export const mockDiscussionWithReplies = [
},
lastEditedBy: null,
userPermissions: {
- adminNote: true,
+ resolveNote: true,
__typename: 'NotePermissions',
},
discussion: {
@@ -282,7 +282,7 @@ export const mockDiscussionWithReplies = [
},
lastEditedBy: null,
userPermissions: {
- adminNote: true,
+ resolveNote: true,
__typename: 'NotePermissions',
},
discussion: {
@@ -368,7 +368,7 @@ export const createAbuseReportNoteResponse = {
},
lastEditedBy: null,
userPermissions: {
- adminNote: true,
+ resolveNote: true,
},
discussion: {
id: 'gid://gitlab/Discussion/90ca230051611e6e1676c50ba7178e0baeabd98d',
@@ -413,7 +413,7 @@ export const editAbuseReportNoteResponse = {
},
lastEditedBy: 'root',
userPermissions: {
- adminNote: true,
+ resolveNote: true,
__typename: 'NotePermissions',
},
},
diff --git a/spec/frontend/deploy_keys/graphql/resolvers_spec.js b/spec/frontend/deploy_keys/graphql/resolvers_spec.js
new file mode 100644
index 00000000000..458232697cb
--- /dev/null
+++ b/spec/frontend/deploy_keys/graphql/resolvers_spec.js
@@ -0,0 +1,249 @@
+import MockAdapter from 'axios-mock-adapter';
+import { HTTP_STATUS_OK, HTTP_STATUS_NOT_FOUND } from '~/lib/utils/http_status';
+import axios from '~/lib/utils/axios_utils';
+import pageInfoQuery from '~/graphql_shared/client/page_info.query.graphql';
+import currentPageQuery from '~/deploy_keys/graphql/queries/current_page.query.graphql';
+import currentScopeQuery from '~/deploy_keys/graphql/queries/current_scope.query.graphql';
+import confirmRemoveKeyQuery from '~/deploy_keys/graphql/queries/confirm_remove_key.query.graphql';
+import { resolvers } from '~/deploy_keys/graphql/resolvers';
+
+const ENDPOINTS = {
+ enabledKeysEndpoint: '/enabled_keys',
+ availableProjectKeysEndpoint: '/available_project_keys',
+ availablePublicKeysEndpoint: '/available_public_keys',
+};
+
+describe('~/deploy_keys/graphql/resolvers', () => {
+ let mockResolvers;
+ let mock;
+ let client;
+
+ beforeEach(() => {
+ mockResolvers = resolvers(ENDPOINTS);
+ mock = new MockAdapter(axios);
+ client = {
+ writeQuery: jest.fn(),
+ readQuery: jest.fn(),
+ readFragment: jest.fn(),
+ cache: { evict: jest.fn(), gc: jest.fn() },
+ };
+ });
+
+ afterEach(() => {
+ mock.reset();
+ });
+
+ describe('deployKeys', () => {
+ const key = { id: 1, title: 'hello', edit_path: '/edit' };
+
+ it.each(['enabledKeys', 'availableProjectKeys', 'availablePublicKeys'])(
+ 'should request the endpoint for the %s scope',
+ async (scope) => {
+ mock.onGet(ENDPOINTS[`${scope}Endpoint`]).reply(HTTP_STATUS_OK, { keys: [key] });
+
+ const keys = await mockResolvers.Project.deployKeys(null, { scope, page: 1 }, { client });
+
+ expect(keys).toEqual([
+ { id: 1, title: 'hello', editPath: '/edit', __typename: 'LocalDeployKey' },
+ ]);
+ },
+ );
+
+ it('should default to enabled keys if a bad scope is given', async () => {
+ const scope = 'bad';
+ mock.onGet(ENDPOINTS.enabledKeysEndpoint).reply(HTTP_STATUS_OK, { keys: [key] });
+
+ const keys = await mockResolvers.Project.deployKeys(null, { scope, page: 1 }, { client });
+
+ expect(keys).toEqual([
+ { id: 1, title: 'hello', editPath: '/edit', __typename: 'LocalDeployKey' },
+ ]);
+ });
+
+ it('should request the given page', async () => {
+ const scope = 'enabledKeys';
+ const page = 2;
+ mock
+ .onGet(ENDPOINTS.enabledKeysEndpoint, { params: { page } })
+ .reply(HTTP_STATUS_OK, { keys: [key] });
+
+ const keys = await mockResolvers.Project.deployKeys(null, { scope, page }, { client });
+
+ expect(keys).toEqual([
+ { id: 1, title: 'hello', editPath: '/edit', __typename: 'LocalDeployKey' },
+ ]);
+ });
+
+ it('should write pagination info to the cache', async () => {
+ const scope = 'enabledKeys';
+ const page = 1;
+
+ mock.onGet(ENDPOINTS.enabledKeysEndpoint).reply(
+ HTTP_STATUS_OK,
+ { keys: [key] },
+ {
+ 'x-next-page': '2',
+ 'x-page': '1',
+ 'X-Per-Page': '2',
+ 'X-Prev-Page': '',
+ 'X-TOTAL': '37',
+ 'X-Total-Pages': '5',
+ },
+ );
+
+ await mockResolvers.Project.deployKeys(null, { scope, page }, { client });
+
+ expect(client.writeQuery).toHaveBeenCalledWith({
+ query: pageInfoQuery,
+ variables: { input: { scope, page } },
+ data: {
+ pageInfo: {
+ total: 37,
+ perPage: 2,
+ previousPage: NaN,
+ totalPages: 5,
+ nextPage: 2,
+ page: 1,
+ __typename: 'LocalPageInfo',
+ },
+ },
+ });
+ });
+
+ it('should not write page info if the request fails', async () => {
+ const scope = 'enabledKeys';
+ const page = 1;
+
+ mock.onGet(ENDPOINTS.enabledKeysEndpoint).reply(HTTP_STATUS_NOT_FOUND);
+
+ try {
+ await mockResolvers.Project.deployKeys(null, { scope, page }, { client });
+ } catch {
+ expect(client.writeQuery).not.toHaveBeenCalled();
+ }
+ });
+ });
+
+ describe('currentPage', () => {
+ it('sets the current page', () => {
+ const page = 5;
+ mockResolvers.Mutation.currentPage(null, { page }, { client });
+
+ expect(client.writeQuery).toHaveBeenCalledWith({
+ query: currentPageQuery,
+ data: { currentPage: page },
+ });
+ });
+ });
+
+ describe('currentScope', () => {
+ let scope;
+
+ beforeEach(() => {
+ scope = 'enabledKeys';
+ mockResolvers.Mutation.currentScope(null, { scope }, { client });
+ });
+
+ it('sets the current scope', () => {
+ expect(client.writeQuery).toHaveBeenCalledWith({
+ query: currentScopeQuery,
+ data: { currentScope: scope },
+ });
+ });
+
+ it('resets the page to 1', () => {
+ expect(client.writeQuery).toHaveBeenCalledWith({
+ query: currentPageQuery,
+ data: { currentPage: 1 },
+ });
+ });
+ });
+
+ describe('disableKey', () => {
+ it('disables the key that is pending confirmation', async () => {
+ const key = { id: 1, title: 'hello', disablePath: '/disable', __typename: 'LocalDeployKey' };
+ client.readQuery.mockReturnValue({ deployKeyToRemove: key });
+ client.readFragment.mockReturnValue(key);
+ mock.onPut(key.disablePath).reply(HTTP_STATUS_OK);
+ await mockResolvers.Mutation.disableKey(null, null, { client });
+
+ expect(client.readQuery).toHaveBeenCalledWith({ query: confirmRemoveKeyQuery });
+ expect(client.readFragment).toHaveBeenCalledWith(
+ expect.objectContaining({ id: `LocalDeployKey:${key.id}` }),
+ );
+ expect(client.cache.evict).toHaveBeenCalledWith({ fieldName: 'deployKeyToRemove' });
+ expect(client.cache.evict).toHaveBeenCalledWith({ id: `LocalDeployKey:${key.id}` });
+ expect(client.cache.gc).toHaveBeenCalled();
+ });
+
+ it('does not remove the key from the cache on fail', async () => {
+ const key = { id: 1, title: 'hello', disablePath: '/disable', __typename: 'LocalDeployKey' };
+ client.readQuery.mockReturnValue({ deployKeyToRemove: key });
+ client.readFragment.mockReturnValue(key);
+ mock.onPut(key.disablePath).reply(HTTP_STATUS_NOT_FOUND);
+
+ try {
+ await mockResolvers.Mutation.disableKey(null, null, { client });
+ } catch {
+ expect(client.readQuery).toHaveBeenCalledWith({ query: confirmRemoveKeyQuery });
+ expect(client.readFragment).toHaveBeenCalledWith(
+ expect.objectContaining({ id: `LocalDeployKey:${key.id}` }),
+ );
+ expect(client.cache.evict).not.toHaveBeenCalled();
+ expect(client.cache.gc).not.toHaveBeenCalled();
+ }
+ });
+ });
+
+ describe('enableKey', () => {
+ it("calls the key's enable path", async () => {
+ const key = { id: 1, title: 'hello', enablePath: '/enable', __typename: 'LocalDeployKey' };
+ client.readQuery.mockReturnValue({ deployKeyToRemove: key });
+ client.readFragment.mockReturnValue(key);
+ mock.onPut(key.enablePath).reply(HTTP_STATUS_OK);
+ await mockResolvers.Mutation.enableKey(null, key, { client });
+
+ expect(client.readFragment).toHaveBeenCalledWith(
+ expect.objectContaining({ id: `LocalDeployKey:${key.id}` }),
+ );
+ expect(client.cache.evict).toHaveBeenCalledWith({ id: `LocalDeployKey:${key.id}` });
+ expect(client.cache.gc).toHaveBeenCalled();
+ });
+
+ it('does not remove the key from the cache on failure', async () => {
+ const key = { id: 1, title: 'hello', enablePath: '/enable', __typename: 'LocalDeployKey' };
+ client.readQuery.mockReturnValue({ deployKeyToRemove: key });
+ client.readFragment.mockReturnValue(key);
+ mock.onPut(key.enablePath).reply(HTTP_STATUS_NOT_FOUND);
+ try {
+ await mockResolvers.Mutation.enableKey(null, key, { client });
+ } catch {
+ expect(client.readFragment).toHaveBeenCalledWith(
+ expect.objectContaining({ id: `LocalDeployKey:${key.id}` }),
+ );
+ expect(client.cache.evict).not.toHaveBeenCalled();
+ expect(client.cache.gc).not.toHaveBeenCalled();
+ }
+ });
+ });
+
+ describe('confirmDisable', () => {
+ it('sets the key to disable', () => {
+ const key = { id: 1, title: 'hello', enablePath: '/enable', __typename: 'LocalDeployKey' };
+ mockResolvers.Mutation.confirmDisable(null, key, { client });
+
+ expect(client.writeQuery).toHaveBeenCalledWith({
+ query: confirmRemoveKeyQuery,
+ data: { deployKeyToRemove: { id: key.id, __type: 'LocalDeployKey' } },
+ });
+ });
+ it('clears the value when null id is passed', () => {
+ mockResolvers.Mutation.confirmDisable(null, { id: null }, { client });
+
+ expect(client.writeQuery).toHaveBeenCalledWith({
+ query: confirmRemoveKeyQuery,
+ data: { deployKeyToRemove: null },
+ });
+ });
+ });
+});
diff --git a/spec/frontend/environments/graphql/resolvers/base_spec.js b/spec/frontend/environments/graphql/resolvers/base_spec.js
index f78146fe48e..939ccc0780c 100644
--- a/spec/frontend/environments/graphql/resolvers/base_spec.js
+++ b/spec/frontend/environments/graphql/resolvers/base_spec.js
@@ -9,7 +9,7 @@ import environmentToStopQuery from '~/environments/graphql/queries/environment_t
import createMockApollo from 'helpers/mock_apollo_helper';
import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.query.graphql';
import isEnvironmentStoppingQuery from '~/environments/graphql/queries/is_environment_stopping.query.graphql';
-import pageInfoQuery from '~/environments/graphql/queries/page_info.query.graphql';
+import pageInfoQuery from '~/graphql_shared/client/page_info.query.graphql';
import { TEST_HOST } from 'helpers/test_constants';
import {
environmentsApp,
diff --git a/spec/frontend/security_configuration/components/app_spec.js b/spec/frontend/security_configuration/components/app_spec.js
index 364fe733a41..94d888bb067 100644
--- a/spec/frontend/security_configuration/components/app_spec.js
+++ b/spec/frontend/security_configuration/components/app_spec.js
@@ -5,10 +5,10 @@ import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import stubChildren from 'helpers/stub_children';
import { mountExtended } from 'helpers/vue_test_utils_helper';
-import SecurityConfigurationApp, { i18n } from '~/security_configuration/components/app.vue';
+import SecurityConfigurationApp from '~/security_configuration/components/app.vue';
import AutoDevopsAlert from '~/security_configuration/components/auto_dev_ops_alert.vue';
import AutoDevopsEnabledAlert from '~/security_configuration/components/auto_dev_ops_enabled_alert.vue';
-import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from '~/security_configuration/components/constants';
+import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from '~/security_configuration/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
import { securityFeaturesMock, provideMock } from '../mock_data';
@@ -19,6 +19,8 @@ const { vulnerabilityTrainingDocsPath, projectFullPath } = provideMock;
useLocalStorageSpy();
Vue.use(VueApollo);
+const { i18n } = SecurityConfigurationApp;
+
describe('~/security_configuration/components/app', () => {
let wrapper;
let userCalloutDismissSpy;
diff --git a/spec/frontend/security_configuration/components/feature_card_spec.js b/spec/frontend/security_configuration/components/feature_card_spec.js
index 983a66a7fd3..9efee2a409a 100644
--- a/spec/frontend/security_configuration/components/feature_card_spec.js
+++ b/spec/frontend/security_configuration/components/feature_card_spec.js
@@ -1,7 +1,7 @@
import { GlIcon } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
-import { securityFeatures } from '~/security_configuration/components/constants';
+import { securityFeatures } from '~/security_configuration/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
import FeatureCardBadge from '~/security_configuration/components/feature_card_badge.vue';
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
diff --git a/spec/frontend/security_configuration/components/training_provider_list_spec.js b/spec/frontend/security_configuration/components/training_provider_list_spec.js
index 5b2b3f46df6..ef20d8f56a4 100644
--- a/spec/frontend/security_configuration/components/training_provider_list_spec.js
+++ b/spec/frontend/security_configuration/components/training_provider_list_spec.js
@@ -19,8 +19,8 @@ import {
TRACK_TOGGLE_TRAINING_PROVIDER_LABEL,
TRACK_PROVIDER_LEARN_MORE_CLICK_ACTION,
TRACK_PROVIDER_LEARN_MORE_CLICK_LABEL,
+ TEMP_PROVIDER_URLS,
} from '~/security_configuration/constants';
-import { TEMP_PROVIDER_URLS } from '~/security_configuration/components/constants';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
import { updateSecurityTrainingOptimisticResponse } from '~/security_configuration/graphql/cache_utils';
import securityTrainingProvidersQuery from '~/security_configuration/graphql/security_training_providers.query.graphql';
@@ -61,10 +61,9 @@ const TEMP_PROVIDER_LOGOS = {
svg: '<svg>Secure Code Warrior</svg>',
},
};
-jest.mock('~/security_configuration/components/constants', () => {
+jest.mock('~/security_configuration/constants', () => {
return {
- TEMP_PROVIDER_URLS: jest.requireActual('~/security_configuration/components/constants')
- .TEMP_PROVIDER_URLS,
+ TEMP_PROVIDER_URLS: jest.requireActual('~/security_configuration/constants').TEMP_PROVIDER_URLS,
// NOTE: Jest hoists all mocks to the top so we can't use TEMP_PROVIDER_LOGOS
// here directly.
TEMP_PROVIDER_LOGOS: {
diff --git a/spec/frontend/security_configuration/mock_data.js b/spec/frontend/security_configuration/mock_data.js
index df10d33e2f0..208256afdbd 100644
--- a/spec/frontend/security_configuration/mock_data.js
+++ b/spec/frontend/security_configuration/mock_data.js
@@ -4,7 +4,7 @@ import {
SAST_DESCRIPTION,
SAST_HELP_PATH,
SAST_CONFIG_HELP_PATH,
-} from '~/security_configuration/components/constants';
+} from '~/security_configuration/constants';
import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants';
export const testProjectPath = 'foo/bar';
diff --git a/spec/frontend/security_configuration/utils_spec.js b/spec/frontend/security_configuration/utils_spec.js
index ea04e9e7993..3c6d4baa30f 100644
--- a/spec/frontend/security_configuration/utils_spec.js
+++ b/spec/frontend/security_configuration/utils_spec.js
@@ -1,5 +1,5 @@
import { augmentFeatures, translateScannerNames } from '~/security_configuration/utils';
-import { SCANNER_NAMES_MAP } from '~/security_configuration/components/constants';
+import { SCANNER_NAMES_MAP } from '~/security_configuration/constants';
describe('augmentFeatures', () => {
const mockSecurityFeatures = [
diff --git a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
index f3d0d66cdd1..2b36344cfa8 100644
--- a/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
+++ b/spec/frontend/vue_shared/security_reports/components/manage_via_mr_spec.js
@@ -2,7 +2,7 @@ import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { featureToMutationMap } from 'ee_else_ce/security_configuration/components/constants';
+import { featureToMutationMap } from 'ee_else_ce/security_configuration/constants';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
diff --git a/spec/graphql/types/permission_types/abuse_report_spec.rb b/spec/graphql/types/permission_types/abuse_report_spec.rb
deleted file mode 100644
index 399df137a78..00000000000
--- a/spec/graphql/types/permission_types/abuse_report_spec.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-RSpec.describe Types::PermissionTypes::AbuseReport, feature_category: :insider_threat do
- it do
- expected_permissions = [
- :read_abuse_report, :create_note
- ]
-
- expected_permissions.each do |permission|
- expect(described_class).to have_graphql_field(permission)
- end
- end
-end
diff --git a/spec/helpers/groups_helper_spec.rb b/spec/helpers/groups_helper_spec.rb
index fe8ed2691fb..8aee337f51c 100644
--- a/spec/helpers/groups_helper_spec.rb
+++ b/spec/helpers/groups_helper_spec.rb
@@ -656,6 +656,41 @@ RSpec.describe GroupsHelper, feature_category: :groups_and_projects do
end
end
+ context 'when a user has different access for different groups in the hierarchy' do
+ let_it_be(:grand_parent) { create(:group) }
+ let_it_be(:parent) { create(:group, parent: grand_parent) }
+ let_it_be(:child) { create(:group, parent: parent) }
+ let_it_be(:grand_child) { create(:group, parent: child) }
+
+ before_all do
+ parent.add_developer(user)
+ child.add_maintainer(user)
+ grand_child.add_owner(user)
+ end
+
+ it 'returns the access levels that are peers or lower' do
+ expect(helper.access_level_roles_user_can_assign(grand_parent)).to be_empty
+ expect(helper.access_level_roles_user_can_assign(parent)).to eq({
+ 'Guest' => ::Gitlab::Access::GUEST,
+ 'Reporter' => ::Gitlab::Access::REPORTER,
+ 'Developer' => ::Gitlab::Access::DEVELOPER
+ })
+ expect(helper.access_level_roles_user_can_assign(child)).to eq(::Gitlab::Access.options)
+ expect(helper.access_level_roles_user_can_assign(grand_child)).to eq(::Gitlab::Access.options_with_owner)
+ end
+ end
+
+ context 'when a group is linked to another' do
+ let_it_be(:other_group) { create(:group) }
+ let_it_be(:group_link) { create(:group_group_link, shared_group: group, shared_with_group: other_group, group_access: Gitlab::Access::MAINTAINER) }
+
+ before_all do
+ other_group.add_owner(user)
+ end
+
+ it { is_expected.to eq(::Gitlab::Access.options) }
+ end
+
context 'when user is not provided' do
before do
allow(helper).to receive(:current_user).and_return(nil)
diff --git a/spec/policies/abuse_report_policy_spec.rb b/spec/policies/abuse_report_policy_spec.rb
index 01ab29d1cf1..3cc92749ab4 100644
--- a/spec/policies/abuse_report_policy_spec.rb
+++ b/spec/policies/abuse_report_policy_spec.rb
@@ -12,6 +12,7 @@ RSpec.describe AbuseReportPolicy, feature_category: :insider_threat do
it 'cannot read_abuse_report' do
expect(policy).to be_disallowed(:read_abuse_report)
+ expect(policy).to be_disallowed(:read_note)
expect(policy).to be_disallowed(:create_note)
end
end
@@ -21,6 +22,7 @@ RSpec.describe AbuseReportPolicy, feature_category: :insider_threat do
it 'can read_abuse_report' do
expect(policy).to be_allowed(:read_abuse_report)
+ expect(policy).to be_allowed(:read_note)
expect(policy).to be_allowed(:create_note)
end
end
diff --git a/spec/requests/api/graphql/abuse_report_spec.rb b/spec/requests/api/graphql/abuse_report_spec.rb
index f74b1fb4061..8ab0e92d838 100644
--- a/spec/requests/api/graphql/abuse_report_spec.rb
+++ b/spec/requests/api/graphql/abuse_report_spec.rb
@@ -25,11 +25,7 @@ RSpec.describe 'Querying an Abuse Report', feature_category: :insider_threat do
it 'returns all fields' do
expect(abuse_report_data).to include(
- 'id' => global_id,
- 'userPermissions' => {
- 'readAbuseReport' => true,
- 'createNote' => true
- }
+ 'id' => global_id
)
end
end
diff --git a/spec/scripts/internal_events/cli_spec.rb b/spec/scripts/internal_events/cli_spec.rb
new file mode 100644
index 00000000000..d84a4498fe8
--- /dev/null
+++ b/spec/scripts/internal_events/cli_spec.rb
@@ -0,0 +1,866 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+require 'tty/prompt/test'
+require_relative '../../../scripts/internal_events/cli'
+
+RSpec.describe Cli, feature_category: :service_ping do
+ let(:prompt) { TTY::Prompt::Test.new }
+ let(:files_to_cleanup) { [] }
+
+ let(:event1_filepath) { 'config/events/internal_events_cli_used.yml' }
+ let(:event1_content) { internal_event_fixture('events/event_with_identifiers.yml') }
+ let(:event2_filepath) { 'ee/config/events/internal_events_cli_opened.yml' }
+ let(:event2_content) { internal_event_fixture('events/ee_event_without_identifiers.yml') }
+ let(:event3_filepath) { 'config/events/internal_events_cli_closed.yml' }
+ let(:event3_content) { internal_event_fixture('events/secondary_event_with_identifiers.yml') }
+
+ before do
+ stub_milestone('16.6')
+ collect_file_writes(files_to_cleanup)
+ stub_product_groups(File.read('spec/fixtures/scripts/internal_events/stages.yml'))
+ stub_helper(:fetch_window_size, '50')
+ end
+
+ after do
+ delete_files(files_to_cleanup)
+ end
+
+ shared_examples 'creates the right defintion files' do |description, test_case = {}|
+ # For expected keystroke mapping, see https://github.com/piotrmurach/tty-reader/blob/master/lib/tty/reader/keys.rb
+ let(:keystrokes) { test_case.dig('inputs', 'keystrokes') || [] }
+ let(:input_files) { test_case.dig('inputs', 'files') || [] }
+ let(:output_files) { test_case.dig('outputs', 'files') || [] }
+
+ subject { run_with_verbose_timeout }
+
+ it "in scenario: #{description}" do
+ delete_old_ouputs # just in case
+ prep_input_files
+ queue_cli_inputs(keystrokes)
+ expect_file_creation
+
+ subject
+ end
+
+ private
+
+ def delete_old_ouputs
+ [input_files, output_files].flatten.each do |file_info|
+ FileUtils.rm_f(Rails.root.join(file_info['path']))
+ end
+ end
+
+ def prep_input_files
+ input_files.each do |file|
+ File.write(
+ Rails.root.join(file['path']),
+ File.read(Rails.root.join(file['content']))
+ )
+ end
+ end
+
+ def expect_file_creation
+ if output_files.any?
+ output_files.each do |file|
+ expect(File).to receive(:write).with(file['path'], File.read(file['content']))
+ end
+ else
+ expect(File).not_to receive(:write)
+ end
+ end
+ end
+
+ context 'when creating new events' do
+ YAML.safe_load(File.read('spec/fixtures/scripts/internal_events/new_events.yml')).each do |test_case|
+ it_behaves_like 'creates the right defintion files', test_case['description'], test_case
+ end
+ end
+
+ context 'when creating new metrics' do
+ YAML.safe_load(File.read('spec/fixtures/scripts/internal_events/new_metrics.yml')).each do |test_case|
+ it_behaves_like 'creates the right defintion files', test_case['description'], test_case
+ end
+
+ context 'when creating a metric from multiple events' do
+ let(:events) do
+ [{
+ action: '00_event1', category: 'InternalEventTracking',
+ product_section: 'dev', product_stage: 'plan', product_group: 'optimize'
+ }, {
+ action: '00_event2', category: 'InternalEventTracking',
+ product_section: 'dev', product_stage: 'create', product_group: 'ide'
+ }, {
+ action: '00_event3', category: 'InternalEventTracking',
+ product_section: 'dev', product_stage: 'create', product_group: 'source_code'
+ }]
+ end
+
+ before do
+ events.each do |event|
+ File.write("config/events/#{event[:action]}.yml", event.transform_keys(&:to_s).to_yaml)
+ end
+ end
+
+ it 'filters the product group options based on common section' do
+ # Select 00_event1 & #00_event2
+ queue_cli_inputs([
+ "2\n", # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ "2\n", # Enum-select: Multiple events -- count occurrences of several separate events or interactions
+ " ", # Multi-select: __event1
+ "\e[B", # Arrow down to: __event2
+ " ", # Multi-select: __event2
+ "\n", # Submit selections
+ "\n", # Select: Weekly/Monthly count of unique users
+ "aggregate metric description\n", # Submit description
+ "\n", # Accept description for weekly
+ "\n" # Copy & continue
+ ])
+
+ run_with_timeout
+
+ # Filter down to "dev" options
+ expect(plain_last_lines(9)).to eq <<~TEXT.chomp
+ ‣ dev:plan:project_management
+ dev:plan:product_planning
+ dev:plan:knowledge
+ dev:plan:optimize
+ dev:create:source_code
+ dev:create:code_review
+ dev:create:ide
+ dev:create:editor_extensions
+ dev:create:code_creation
+ TEXT
+ end
+
+ it 'filters the product group options based on common section & stage' do
+ # Select 00_event2 & #00_event3
+ queue_cli_inputs([
+ "2\n", # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ "2\n", # Enum-select: Multiple events -- count occurrences of several separate events or interactions
+ "\e[B", # Arrow down to: __event2
+ " ", # Multi-select: __event2
+ "\e[B", # Arrow down to: __event3
+ " ", # Multi-select: __event3
+ "\n", # Submit selections
+ "\n", # Select: Weekly/Monthly count of unique users
+ "aggregate metric description\n", # Submit description
+ "\n", # Accept description for weekly
+ "\n" # Copy & continue
+ ])
+
+ run_with_timeout
+
+ # Filter down to "dev:create" options
+ expect(plain_last_lines(5)).to eq <<~TEXT.chomp
+ ‣ dev:create:source_code
+ dev:create:code_review
+ dev:create:ide
+ dev:create:editor_extensions
+ dev:create:code_creation
+ TEXT
+ end
+ end
+
+ context 'when product group for event no longer exists' do
+ let(:event) do
+ {
+ action: '00_event1', category: 'InternalEventTracking',
+ product_section: 'other', product_stage: 'other', product_group: 'other'
+ }
+ end
+
+ before do
+ File.write("config/events/#{event[:action]}.yml", event.transform_keys(&:to_s).to_yaml)
+ end
+
+ it 'prompts user to select another group' do
+ queue_cli_inputs([
+ "2\n", # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ "1\n", # Enum-select: Single event -- count occurrences of a specific event or user interaction
+ "\n", # Select: 00__event1
+ "\n", # Select: Weekly/Monthly count of unique users
+ "aggregate metric description\n", # Submit description
+ "\n", # Accept description for weekly
+ "2\n" # Modify attributes
+ ])
+
+ run_with_timeout
+
+ # Filter down to "dev" options
+ expect(plain_last_lines(50)).to include 'Select one: Which group owns the metric?'
+ end
+ end
+
+ context 'when creating a metric for an event which has metrics' do
+ before do
+ File.write(event1_filepath, File.read(event1_content))
+ end
+
+ it 'shows all metrics options' do
+ select_event_from_list
+
+ expect(plain_last_lines(5)).to eq <<~TEXT.chomp
+ ‣ Monthly/Weekly count of unique users [who triggered internal_events_cli_used]
+ Monthly/Weekly count of unique projects [where internal_events_cli_used occurred]
+ Monthly/Weekly count of unique namespaces [where internal_events_cli_used occurred]
+ Monthly/Weekly count of [internal_events_cli_used occurrences]
+ Total count of [internal_events_cli_used occurrences]
+ TEXT
+ end
+
+ context 'with an existing weekly metric' do
+ before do
+ File.write(
+ 'ee/config/metrics/counts_7d/count_total_internal_events_cli_used_weekly.yml',
+ File.read('spec/fixtures/scripts/internal_events/metrics/ee_total_7d_single_event.yml')
+ )
+ end
+
+ it 'partially filters metric options' do
+ select_event_from_list
+
+ expect(plain_last_lines(6)).to eq <<~TEXT.chomp
+ ‣ Monthly/Weekly count of unique users [who triggered internal_events_cli_used]
+ Monthly/Weekly count of unique projects [where internal_events_cli_used occurred]
+ Monthly/Weekly count of unique namespaces [where internal_events_cli_used occurred]
+ Monthly count of [internal_events_cli_used occurrences]
+ ✘ Weekly count of [internal_events_cli_used occurrences] (already defined)
+ Total count of [internal_events_cli_used occurrences]
+ TEXT
+ end
+ end
+
+ context 'with an existing total metric' do
+ before do
+ File.write(
+ 'ee/config/metrics/counts_all/count_total_internal_events_cli_used.yml',
+ File.read('spec/fixtures/scripts/internal_events/metrics/ee_total_single_event.yml')
+ )
+ end
+
+ it 'filters whole metric options' do
+ select_event_from_list
+
+ expect(plain_last_lines(5)).to eq <<~TEXT.chomp
+ ‣ Monthly/Weekly count of unique users [who triggered internal_events_cli_used]
+ Monthly/Weekly count of unique projects [where internal_events_cli_used occurred]
+ Monthly/Weekly count of unique namespaces [where internal_events_cli_used occurred]
+ Monthly/Weekly count of [internal_events_cli_used occurrences]
+ ✘ Total count of [internal_events_cli_used occurrences] (already defined)
+ TEXT
+ end
+ end
+
+ private
+
+ def select_event_from_list
+ queue_cli_inputs([
+ "2\n", # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ "1\n", # Enum-select: Single event -- count occurrences of a specific event or user interaction
+ 'internal_events_cli_used', # Filters to this event
+ "\n" # Select: config/events/internal_events_cli_used.yml
+ ])
+
+ run_with_timeout
+ end
+ end
+
+ context 'when event excludes identifiers' do
+ before do
+ File.write(event2_filepath, File.read(event2_content))
+ end
+
+ it 'filters unavailable identifiers' do
+ queue_cli_inputs([
+ "2\n", # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ "1\n", # Enum-select: Single event -- count occurrences of a specific event or user interaction
+ 'internal_events_cli_opened', # Filters to this event
+ "\n" # Select: config/events/internal_events_cli_opened.yml
+ ])
+
+ run_with_timeout
+
+ expect(plain_last_lines(5)).to eq <<~TEXT.chomp
+ ✘ Monthly/Weekly count of unique users [who triggered internal_events_cli_opened] (user unavailable)
+ ✘ Monthly/Weekly count of unique projects [where internal_events_cli_opened occurred] (project unavailable)
+ ✘ Monthly/Weekly count of unique namespaces [where internal_events_cli_opened occurred] (namespace unavailable)
+ ‣ Monthly/Weekly count of [internal_events_cli_opened occurrences]
+ Total count of [internal_events_cli_opened occurrences]
+ TEXT
+ end
+ end
+
+ context 'when all metrics already exist' do
+ let(:event) { { action: '00_event1', category: 'InternalEventTracking' } }
+ let(:metric) { { options: { 'events' => ['00_event1'] }, events: [{ 'name' => '00_event1' }] } }
+
+ let(:files) do
+ [
+ ['config/events/00_event1.yml', event],
+ ['config/metrics/counts_all/count_total_00_event1.yml', metric.merge(time_frame: 'all')],
+ ['config/metrics/counts_7d/count_total_00_event1_weekly.yml', metric.merge(time_frame: '7d')],
+ ['config/metrics/counts_28d/count_total_00_event1_monthly.yml', metric.merge(time_frame: '28d')]
+ ]
+ end
+
+ before do
+ files.each do |path, content|
+ File.write(path, content.transform_keys(&:to_s).to_yaml)
+ end
+ end
+
+ it 'exits the script and directs user to search for existing metrics' do
+ queue_cli_inputs([
+ "2\n", # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ "1\n", # Enum-select: Single event -- count occurrences of a specific event or user interaction
+ '00_event1', # Filters to this event
+ "\n" # Select: config/events/00_event1.yml
+ ])
+
+ run_with_timeout
+
+ expect(plain_last_lines(15)).to include 'Looks like the potential metrics for this event ' \
+ 'either already exist or are unsupported.'
+ end
+ end
+ end
+
+ context 'when showing usage examples' do
+ let(:expected_example_prompt) do
+ <<~TEXT.chomp
+ Select one: Select a use-case to view examples for: (Press ↑/↓ arrow or 1-8 number to move and Enter to select)
+ ‣ 1. ruby/rails
+ 2. rspec
+ 3. javascript (vue)
+ 4. javascript (plain)
+ 5. vue template
+ 6. haml
+ 7. View examples for a different event
+ 8. Exit
+ TEXT
+ end
+
+ context 'for an event with identifiers' do
+ let(:expected_rails_example) do
+ <<~TEXT.chomp
+ --------------------------------------------------
+ # RAILS
+
+ Gitlab::InternalEvents.track_event(
+ 'internal_events_cli_used',
+ project: project,
+ namespace: project.namespace,
+ user: user
+ )
+
+ --------------------------------------------------
+ TEXT
+ end
+
+ let(:expected_rspec_example) do
+ <<~TEXT.chomp
+ --------------------------------------------------
+ # RSPEC
+
+ it_behaves_like 'internal event tracking' do
+ let(:event) { 'internal_events_cli_used' }
+ let(:project) { project }
+ let(:namespace) { project.namespace }
+ let(:user) { user }
+ end
+
+ --------------------------------------------------
+ TEXT
+ end
+
+ before do
+ File.write(event1_filepath, File.read(event1_content))
+ end
+
+ it 'shows backend examples' do
+ queue_cli_inputs([
+ "3\n", # Enum-select: View Usage -- look at code examples for an existing event
+ 'internal_events_cli_used', # Filters to this event
+ "\n", # Select: config/events/internal_events_cli_used.yml
+ "\n", # Select: ruby/rails
+ "\e[B", # Arrow down to: rspec
+ "\n", # Select: rspec
+ "8\n" # Exit
+ ])
+
+ run_with_timeout
+
+ output = plain_last_lines(100)
+
+ expect(output).to include expected_example_prompt
+ expect(output).to include expected_rails_example
+ expect(output).to include expected_rspec_example
+ end
+ end
+
+ context 'for an event without identifiers' do
+ let(:expected_rails_example) do
+ <<~TEXT.chomp
+ --------------------------------------------------
+ # RAILS
+
+ Gitlab::InternalEvents.track_event('internal_events_cli_opened')
+
+ --------------------------------------------------
+ TEXT
+ end
+
+ let(:expected_rspec_example) do
+ <<~TEXT.chomp
+ --------------------------------------------------
+ # RSPEC
+
+ it_behaves_like 'internal event tracking' do
+ let(:event) { 'internal_events_cli_opened' }
+ end
+
+ --------------------------------------------------
+ TEXT
+ end
+
+ let(:expected_vue_example) do
+ <<~TEXT.chomp
+ --------------------------------------------------
+ // VUE
+
+ <script>
+ import { InternalEvents } from '~/tracking';
+ import { GlButton } from '@gitlab/ui';
+
+ const trackingMixin = InternalEvents.mixin();
+
+ export default {
+ mixins: [trackingMixin],
+ components: { GlButton },
+ methods: {
+ performAction() {
+ this.trackEvent('internal_events_cli_opened');
+ },
+ },
+ };
+ </script>
+
+ <template>
+ <gl-button @click=performAction>Click Me</gl-button>
+ </template>
+
+ --------------------------------------------------
+ TEXT
+ end
+
+ let(:expected_js_example) do
+ <<~TEXT.chomp
+ --------------------------------------------------
+ // FRONTEND -- RAW JAVASCRIPT
+
+ import { InternalEvents } from '~/tracking';
+
+ export const performAction = () => {
+ InternalEvents.trackEvent('internal_events_cli_opened');
+
+ return true;
+ };
+
+ --------------------------------------------------
+ TEXT
+ end
+
+ let(:expected_vue_template_example) do
+ <<~TEXT.chomp
+ --------------------------------------------------
+ // VUE TEMPLATE -- ON-CLICK
+
+ <script>
+ import { GlButton } from '@gitlab/ui';
+
+ export default {
+ components: { GlButton }
+ };
+ </script>
+
+ <template>
+ <gl-button data-event-tracking="internal_events_cli_opened">
+ Click Me
+ </gl-button>
+ </template>
+
+ --------------------------------------------------
+ // VUE TEMPLATE -- ON-LOAD
+
+ <script>
+ import { GlButton } from '@gitlab/ui';
+
+ export default {
+ components: { GlButton }
+ };
+ </script>
+
+ <template>
+ <gl-button data-event-tracking-load="internal_events_cli_opened">
+ Click Me
+ </gl-button>
+ </template>
+
+ --------------------------------------------------
+ TEXT
+ end
+
+ let(:expected_haml_example) do
+ <<~TEXT.chomp
+ --------------------------------------------------
+ # HAML -- ON-CLICK
+
+ .gl-display-inline-block{ data: { event_tracking: 'internal_events_cli_opened' } }
+ = _('Important Text')
+
+ --------------------------------------------------
+ # HAML -- COMPONENT ON-CLICK
+
+ = render Pajamas::ButtonComponent.new(button_options: { data: { event_tracking: 'internal_events_cli_opened' } })
+
+ --------------------------------------------------
+ # HAML -- COMPONENT ON-LOAD
+
+ = render Pajamas::ButtonComponent.new(button_options: { data: { event_tracking_load: true, event_tracking: 'internal_events_cli_opened' } })
+
+ --------------------------------------------------
+ TEXT
+ end
+
+ before do
+ File.write(event2_filepath, File.read(event2_content))
+ end
+
+ it 'shows all examples' do
+ queue_cli_inputs([
+ "3\n", # Enum-select: View Usage -- look at code examples for an existing event
+ 'internal_events_cli_opened', # Filters to this event
+ "\n", # Select: config/events/internal_events_cli_used.yml
+ "\n", # Select: ruby/rails
+ "\e[B", # Arrow down to: rspec
+ "\n", # Select: rspec
+ "\e[B", # Arrow down to: js vue
+ "\n", # Select: js vue
+ "\e[B", # Arrow down to: js plain
+ "\n", # Select: js plain
+ "\e[B", # Arrow down to: vue template
+ "\n", # Select: vue template
+ "\e[B", # Arrow down to: haml
+ "\n", # Select: haml
+ "8\n" # Exit
+ ])
+
+ run_with_timeout
+
+ output = plain_last_lines(1000)
+
+ expect(output).to include expected_example_prompt
+ expect(output).to include expected_rails_example
+ expect(output).to include expected_rspec_example
+ expect(output).to include expected_vue_example
+ expect(output).to include expected_js_example
+ expect(output).to include expected_vue_template_example
+ expect(output).to include expected_haml_example
+ end
+ end
+
+ context 'when viewing examples for multiple events' do
+ let(:expected_event1_example) do
+ <<~TEXT.chomp
+ --------------------------------------------------
+ # RAILS
+
+ Gitlab::InternalEvents.track_event(
+ 'internal_events_cli_used',
+ project: project,
+ namespace: project.namespace,
+ user: user
+ )
+
+ --------------------------------------------------
+ TEXT
+ end
+
+ let(:expected_event2_example) do
+ <<~TEXT.chomp
+ --------------------------------------------------
+ # RAILS
+
+ Gitlab::InternalEvents.track_event('internal_events_cli_opened')
+
+ --------------------------------------------------
+ TEXT
+ end
+
+ before do
+ File.write(event1_filepath, File.read(event1_content))
+ File.write(event2_filepath, File.read(event2_content))
+ end
+
+ it 'switches between events gracefully' do
+ queue_cli_inputs([
+ "3\n", # Enum-select: View Usage -- look at code examples for an existing event
+ 'internal_events_cli_used', # Filters to this event
+ "\n", # Select: config/events/internal_events_cli_used.yml
+ "\n", # Select: ruby/rails
+ "7\n", # Select: View examples for a different event
+ 'internal_events_cli_opened', # Filters to this event
+ "\n", # Select: config/events/internal_events_cli_opened.yml
+ "\n", # Select: ruby/rails
+ "8\n" # Exit
+ ])
+
+ run_with_timeout
+
+ output = plain_last_lines(300)
+
+ expect(output).to include expected_example_prompt
+ expect(output).to include expected_event1_example
+ expect(output).to include expected_event2_example
+ end
+ end
+ end
+
+ context 'when offline' do
+ before do
+ stub_product_groups(nil)
+ end
+
+ it_behaves_like 'creates the right defintion files',
+ 'Creates a new event with product stage/section/group input manually' do
+ let(:keystrokes) do
+ [
+ "1\n", # Enum-select: New Event -- start tracking when an action or scenario occurs on gitlab instances
+ "Internal Event CLI is opened\n", # Submit description
+ "internal_events_cli_opened\n", # Submit action name
+ "6\n", # Select: None
+ "\n", # Skip MR URL
+ "analytics\n", # Input section
+ "monitor\n", # Input stage
+ "analytics_instrumentation\n", # Input group
+ "2\n", # Select [premium, ultimate]
+ "y\n", # Create file
+ "3\n" # Exit
+ ]
+ end
+
+ let(:output_files) { [{ 'path' => event2_filepath, 'content' => event2_content }] }
+ end
+
+ it_behaves_like 'creates the right defintion files',
+ 'Creates a new metric with product stage/section/group input manually' do
+ let(:keystrokes) do
+ [
+ "2\n", # Enum-select: New Metric -- calculate how often one or more existing events occur over time
+ "2\n", # Enum-select: Multiple events -- count occurrences of several separate events or interactions
+ 'internal_events_cli', # Filters to the relevant events
+ ' ', # Multi-select: internal_events_cli_closed
+ "\e[B", # Arrow down to: internal_events_cli_used
+ ' ', # Multi-select: internal_events_cli_used
+ "\n", # Submit selections
+ "\e[B", # Arrow down to: Weekly count of unique projects
+ "\n", # Select: Weekly count of unique projects
+ "where a defition file was created with the CLI\n", # Input description
+ "\n", # Submit weekly description for monthly
+ "2\n", # Select: Modify attributes
+ "\n", # Accept section
+ "\n", # Accept stage
+ "\n", # Accept group
+ "\n", # Skip URL
+ "1\n", # Select: [free, premium, ultimate]
+ "y\n", # Create file
+ "y\n", # Create file
+ "2\n" # Exit
+ ]
+ end
+
+ let(:input_files) do
+ [
+ { 'path' => event1_filepath, 'content' => event1_content },
+ { 'path' => event3_filepath, 'content' => event3_content }
+ ]
+ end
+
+ let(:output_files) do
+ # rubocop:disable Layout/LineLength -- Long filepaths read better unbroken
+ [{
+ 'path' => 'config/metrics/counts_28d/count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_monthly.yml',
+ 'content' => 'spec/fixtures/scripts/internal_events/metrics/project_id_28d_multiple_events.yml'
+ }, {
+ 'path' => 'config/metrics/counts_7d/count_distinct_project_id_from_internal_events_cli_closed_and_internal_events_cli_used_weekly.yml',
+ 'content' => 'spec/fixtures/scripts/internal_events/metrics/project_id_7d_multiple_events.yml'
+ }]
+ # rubocop:enable Layout/LineLength
+ end
+ end
+ end
+
+ context 'when window size is unavailable' do
+ before do
+ # `tput <cmd>` returns empty string on error
+ stub_helper(:fetch_window_size, '')
+ stub_helper(:fetch_window_height, '')
+ end
+
+ it_behaves_like 'creates the right defintion files',
+ 'Terminal size does not prevent file creation' do
+ let(:keystrokes) do
+ [
+ "1\n", # Enum-select: New Event -- start tracking when an action or scenario occurs on gitlab instances
+ "Internal Event CLI is opened\n", # Submit description
+ "internal_events_cli_opened\n", # Submit action name
+ "6\n", # Select: None
+ "\n", # Skip MR URL
+ "instrumentation\n", # Filter & select group
+ "2\n", # Select [premium, ultimate]
+ "y\n", # Create file
+ "3\n" # Exit
+ ]
+ end
+
+ let(:output_files) { [{ 'path' => event2_filepath, 'content' => event2_content }] }
+ end
+ end
+
+ context "when user doesn't know what they're trying to do" do
+ it "handles when user isn't trying to track product usage" do
+ queue_cli_inputs([
+ "4\n", # Enum-select: ...am I in the right place?
+ "n\n" # No --> Are you trying to track customer usage of a GitLab feature?
+ ])
+
+ run_with_timeout
+
+ expect(plain_last_lines(50)).to include("Oh no! This probably isn't the tool you need!")
+ end
+
+ it "handles when product usage can't be tracked with events" do
+ queue_cli_inputs([
+ "4\n", # Enum-select: ...am I in the right place?
+ "y\n", # Yes --> Are you trying to track customer usage of a GitLab feature?
+ "n\n" # No --> Can usage for the feature be measured by tracking a specific user action?
+ ])
+
+ run_with_timeout
+
+ expect(plain_last_lines(50)).to include("Oh no! This probably isn't the tool you need!")
+ end
+
+ it 'handles when user needs to add a new event' do
+ queue_cli_inputs([
+ "4\n", # Enum-select: ...am I in the right place?
+ "y\n", # Yes --> Are you trying to track customer usage of a GitLab feature?
+ "y\n", # Yes --> Can usage for the feature be measured by tracking a specific user action?
+ "n\n", # No --> Is the event already tracked?
+ "n\n" # No --> Ready to start?
+ ])
+
+ run_with_timeout
+
+ expect(plain_last_lines(30)).to include("Okay! The next step is adding a new event! (~5 min)")
+ end
+
+ it 'handles when user needs to add a new metric' do
+ queue_cli_inputs([
+ "4\n", # Enum-select: ...am I in the right place?
+ "y\n", # Yes --> Are you trying to track customer usage of a GitLab feature?
+ "y\n", # Yes --> Can usage for the feature be measured by tracking a specific user action?
+ "y\n", # Yes --> Is the event already tracked?
+ "n\n" # No --> Ready to start?
+ ])
+
+ run_with_timeout
+
+ expect(plain_last_lines(30)).to include("Amazing! The next step is adding a new metric! (~8 min)")
+ end
+ end
+
+ private
+
+ def queue_cli_inputs(keystrokes)
+ prompt.input << keystrokes.join('')
+ prompt.input.rewind
+ end
+
+ def run_with_timeout(duration = 1)
+ Timeout.timeout(duration) { described_class.new(prompt).run }
+ rescue Timeout::Error
+ # Timeout is needed to break out of the CLI, but we may want
+ # to make assertions afterwards
+ end
+
+ def run_with_verbose_timeout(duration = 1)
+ Timeout.timeout(duration) { described_class.new(prompt).run }
+ rescue Timeout::Error => e
+ # Re-raise error so CLI output is printed with the error
+ message = <<~TEXT
+ Awaiting input too long. Entire CLI output:
+
+ #{
+ prompt.output.string.lines
+ .map { |line| "\e[0;37m#{line}\e[0m" } # wrap in white
+ .join('')
+ .gsub("\e[1G", "\e[1G ") # align to error indent
+ }
+
+
+ TEXT
+
+ raise e.class, message, e.backtrace
+ end
+
+ def plain_last_lines(size)
+ prompt.output.string
+ .lines
+ .last(size)
+ .join('')
+ .gsub(/\e[^\sm]{2,4}[mh]/, '')
+ end
+
+ def collect_file_writes(collector)
+ allow(File).to receive(:write).and_wrap_original do |original_method, *args, &block|
+ filepath = args.first
+ collector << filepath
+
+ dirname = Pathname.new(filepath).dirname
+ unless dirname.directory?
+ FileUtils.mkdir_p dirname
+ collector << dirname.to_s
+ end
+
+ original_method.call(*args, &block)
+ end
+ end
+
+ def stub_milestone(milestone)
+ stub_const("InternalEventsCli::Helpers::MILESTONE", milestone)
+ end
+
+ def stub_product_groups(body)
+ allow(Net::HTTP).to receive(:get)
+ .with(URI('https://gitlab.com/gitlab-com/www-gitlab-com/-/raw/master/data/stages.yml'))
+ .and_return(body)
+ end
+
+ def stub_helper(helper, value)
+ # rubocop:disable RSpec/AnyInstanceOf -- 'Next' helper not included in fast_spec_helper & next is insufficient
+ allow_any_instance_of(InternalEventsCli::Helpers).to receive(helper).and_return(value)
+ # rubocop:enable RSpec/AnyInstanceOf
+ end
+
+ def delete_files(files)
+ files.each do |filepath|
+ FileUtils.rm_f(Rails.root.join(filepath))
+ end
+ end
+
+ def internal_event_fixture(filepath)
+ Rails.root.join('spec', 'fixtures', 'scripts', 'internal_events', filepath)
+ end
+end
diff --git a/spec/serializers/merge_request_poll_widget_entity_spec.rb b/spec/serializers/merge_request_poll_widget_entity_spec.rb
index 6b80609c348..171f2324cf1 100644
--- a/spec/serializers/merge_request_poll_widget_entity_spec.rb
+++ b/spec/serializers/merge_request_poll_widget_entity_spec.rb
@@ -22,6 +22,9 @@ RSpec.describe MergeRequestPollWidgetEntity do
.to eq(resource.default_merge_commit_message(include_description: true))
end
+ it { is_expected.to include(ff_only_enabled: false) }
+ it { is_expected.to include(ff_merge_possible: false) }
+
describe 'new_blob_path' do
context 'when user can push to project' do
it 'returns path' do