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
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2024-01-23 12:08:09 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2024-01-23 12:08:09 +0300
commit5831f05b4ce3e5af23c98a8c9495419509df6d62 (patch)
tree9b797e9fe9f0d32972b92072962e0838135a117f
parent784a3db6274bf16a64d2cd947d42182c85cf605f (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/groups/components/group_item.vue27
-rw-r--r--app/assets/javascripts/groups/components/item_caret.vue27
-rw-r--r--app/assets/stylesheets/pages/groups.scss6
-rw-r--r--app/controllers/admin/hooks_controller.rb2
-rw-r--r--app/controllers/concerns/web_hooks/hook_actions.rb2
-rw-r--r--app/models/cloud_connector/service_access_token.rb (renamed from app/models/ai/service_access_token.rb)2
-rw-r--r--app/views/admin/hooks/_form.html.haml6
-rw-r--r--app/views/shared/web_hooks/_form.html.haml6
-rw-r--r--app/views/shared/web_hooks/_hook.html.haml14
-rw-r--r--config/events/schema.json3
-rw-r--r--db/docs/service_access_tokens.yml2
-rw-r--r--db/migrate/20240106000000_migrate_data_from_workspaces_url_column.rb59
-rw-r--r--db/migrate/20240116161955_add_name_and_description_to_web_hooks.rb13
-rw-r--r--db/migrate/20240116162201_add_text_limit_to_web_hooks_attributes.rb17
-rw-r--r--db/schema_migrations/202401060000001
-rw-r--r--db/schema_migrations/202401161619551
-rw-r--r--db/schema_migrations/202401161622011
-rw-r--r--db/structure.sql6
-rw-r--r--doc/architecture/blueprints/security_policies_database_read_model/index.md256
-rw-r--r--doc/architecture/blueprints/security_policy_custom_ci/index.md127
-rw-r--r--doc/development/ai_features/index.md2
-rw-r--r--doc/development/internal_analytics/internal_event_instrumentation/event_definition_guide.md2
-rw-r--r--locale/gitlab.pot12
-rwxr-xr-xscripts/internal_events/cli/event_definer.rb4
-rw-r--r--spec/factories/ai/service_access_tokens.rb2
-rw-r--r--spec/features/dashboard/groups_list_spec.rb2
-rw-r--r--spec/frontend/groups/components/group_item_spec.js1
-rw-r--r--spec/frontend/groups/components/item_caret_spec.js41
-rw-r--r--spec/models/cloud_connector/service_access_token_spec.rb (renamed from spec/models/ai/service_access_token_spec.rb)14
-rw-r--r--spec/scripts/internal_events/cli_spec.rb12
30 files changed, 564 insertions, 106 deletions
diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue
index 6e347a3c95b..6f7dce8f9e6 100644
--- a/app/assets/javascripts/groups/components/group_item.vue
+++ b/app/assets/javascripts/groups/components/group_item.vue
@@ -3,9 +3,9 @@ import {
GlAvatar,
GlLoadingIcon,
GlBadge,
+ GlButton,
GlIcon,
GlLabel,
- GlButton,
GlPopover,
GlLink,
GlTooltipDirective,
@@ -27,7 +27,6 @@ import { ITEM_TYPE, ACTIVE_TAB_SHARED } from '../constants';
import eventHub from '../event_hub';
import ItemActions from './item_actions.vue';
-import ItemCaret from './item_caret.vue';
import ItemStats from './item_stats.vue';
import ItemTypeIcon from './item_type_icon.vue';
@@ -39,14 +38,13 @@ export default {
components: {
GlAvatar,
GlBadge,
+ GlButton,
GlLoadingIcon,
GlIcon,
GlLabel,
- GlButton,
GlPopover,
GlLink,
UserAccessRoleBadge,
- ItemCaret,
ItemTypeIcon,
ItemActions,
ItemStats,
@@ -125,6 +123,12 @@ export default {
VISIBILITY_LEVELS_STRING_TO_INTEGER[this.currentGroupVisibility]
);
},
+ toggleAriaLabel() {
+ return this.group.isOpen ? this.$options.i18n.collapse : this.$options.i18n.expand;
+ },
+ toggleIconName() {
+ return this.group.isOpen ? 'chevron-down' : 'chevron-right';
+ },
},
methods: {
onClickRowGroup(e) {
@@ -142,6 +146,8 @@ export default {
},
},
i18n: {
+ expand: __('Expand'),
+ collapse: __('Collapse'),
popoverTitle: __('Less restrictive visibility'),
popoverBody: __('Project visibility level is less restrictive than the group settings.'),
learnMore: __('Learn more'),
@@ -173,7 +179,16 @@ export default {
class="group-row-contents d-flex align-items-center py-2 pr-3"
>
<div class="folder-toggle-wrap gl-mr-2 d-flex align-items-center">
- <item-caret :is-group-open="group.isOpen" />
+ <gl-button
+ v-if="hasChildren"
+ :aria-label="toggleAriaLabel"
+ :aria-expanded="String(group.isOpen)"
+ category="tertiary"
+ data-testid="group-item-toggle-button"
+ :icon="toggleIconName"
+ @click.stop="onClickRowGroup"
+ />
+ <div v-else class="gl-h-7 gl-w-7"></div>
<item-type-icon :item-type="group.type" />
</div>
<gl-loading-icon
@@ -215,7 +230,7 @@ export default {
{{ group.name }}
</a>
<gl-icon
- v-gl-tooltip.hover.bottom
+ v-gl-tooltip.bottom
class="gl-display-inline-flex gl-align-items-center gl-mr-3 gl-text-gray-500"
:name="visibilityIcon"
:title="visibilityTooltip"
diff --git a/app/assets/javascripts/groups/components/item_caret.vue b/app/assets/javascripts/groups/components/item_caret.vue
deleted file mode 100644
index ef82e6d693a..00000000000
--- a/app/assets/javascripts/groups/components/item_caret.vue
+++ /dev/null
@@ -1,27 +0,0 @@
-<script>
-import { GlIcon } from '@gitlab/ui';
-
-export default {
- components: {
- GlIcon,
- },
- props: {
- isGroupOpen: {
- type: Boolean,
- required: true,
- default: false,
- },
- },
- computed: {
- iconClass() {
- return this.isGroupOpen ? 'chevron-down' : 'chevron-right';
- },
- },
-};
-</script>
-
-<template>
- <span class="folder-caret gl-display-inline-block gl-text-secondary gl-w-5 gl-mr-2">
- <gl-icon :size="12" :name="iconClass" />
- </span>
-</template>
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 91f123d7b26..1b51c7f6ba9 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -123,12 +123,6 @@ table.pipeline-project-metrics tr td {
width: 20px;
}
- > .group-row:not(.has-children) {
- .folder-caret {
- opacity: 0;
- }
- }
-
.group-list-tree {
margin-bottom: 0;
margin-left: 30px;
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index c6c0e7eac90..589dd9b324d 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -24,7 +24,7 @@ class Admin::HooksController < Admin::ApplicationController
end
def hook_param_names
- %i[enable_ssl_verification token url]
+ %i[enable_ssl_verification name description token url]
end
def trigger_values
diff --git a/app/controllers/concerns/web_hooks/hook_actions.rb b/app/controllers/concerns/web_hooks/hook_actions.rb
index 076347922c8..5aeb10dfb87 100644
--- a/app/controllers/concerns/web_hooks/hook_actions.rb
+++ b/app/controllers/concerns/web_hooks/hook_actions.rb
@@ -71,7 +71,7 @@ module WebHooks
end
def hook_param_names
- %i[enable_ssl_verification token url push_events_branch_filter branch_filter_strategy]
+ %i[enable_ssl_verification name description token url push_events_branch_filter branch_filter_strategy]
end
def destroy_hook(hook)
diff --git a/app/models/ai/service_access_token.rb b/app/models/cloud_connector/service_access_token.rb
index d2d64079c74..e026b10ec0c 100644
--- a/app/models/ai/service_access_token.rb
+++ b/app/models/cloud_connector/service_access_token.rb
@@ -1,6 +1,6 @@
# frozen_string_literal: true
-module Ai
+module CloudConnector
class ServiceAccessToken < ApplicationRecord
self.table_name = 'service_access_tokens'
diff --git a/app/views/admin/hooks/_form.html.haml b/app/views/admin/hooks/_form.html.haml
index 92a664e1ca8..9f71297c700 100644
--- a/app/views/admin/hooks/_form.html.haml
+++ b/app/views/admin/hooks/_form.html.haml
@@ -5,6 +5,12 @@
= form.text_field :url, class: 'form-control gl-form-input'
%p.form-text.text-muted= _('URL must be percent-encoded if necessary.')
.form-group
+ = form.label :name, s_('Webhooks|Name (optional)'), class: 'label-bold'
+ = form.text_field :name, value: hook.name, class: 'form-control gl-form-input gl-form-input-xl'
+.form-group
+ = form.label :description, s_('Webhooks|Description (optional)'), class: 'label-bold'
+ = form.text_area :description, value: hook.description, class: 'form-control gl-form-input gl-form-input-xl', rows: 4, maxlength: 2048
+.form-group
= form.label :token, _('Secret token'), class: 'label-bold'
= form.password_field :token, value: hook.masked_token, autocomplete: 'new-password', class: 'form-control gl-form-input gl-max-w-48'
%p.form-text.text-muted= _('Use this token to validate received payloads.')
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index a3dfc6eb042..cdef45a0415 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -2,6 +2,12 @@
.js-vue-webhook-form{ data: webhook_form_data(hook) }
.form-group
+ = form.label :name, s_('Webhooks|Name (optional)'), class: 'label-bold'
+ = form.text_field :name, value: hook.name, class: 'form-control gl-form-input gl-form-input-xl'
+.form-group
+ = form.label :description, s_('Webhooks|Description (optional)'), class: 'label-bold'
+ = form.text_area :description, value: hook.description, class: 'form-control gl-form-input gl-form-input-xl', rows: 4, maxlength: 2048
+.form-group
= form.label :token, s_('Webhooks|Secret token'), class: 'label-bold'
= form.password_field :token, value: hook.masked_token, autocomplete: 'new-password', class: 'form-control gl-form-input gl-form-input-xl'
%p.form-text.text-muted
diff --git a/app/views/shared/web_hooks/_hook.html.haml b/app/views/shared/web_hooks/_hook.html.haml
index a332fd9cce7..6c6ff5f7fc8 100644
--- a/app/views/shared/web_hooks/_hook.html.haml
+++ b/app/views/shared/web_hooks/_hook.html.haml
@@ -2,10 +2,13 @@
- sslBadgeText = _('SSL Verification:') + ' ' + sslStatus
%li.gl-border-b.gl-last-of-type-border-b-0
- .gl-display-flex.lgl-align-items-center.row.gl-mx-0
- .col-md-8.col-lg-7.gl-px-5
- .light-header.gl-mb-2
+ .row.gl-mx-0
+ .col-md-8.col-lg-5.gl-px-5
+ .light-header.gl-mb-1
+ = hook.name
+ .gl-font-sm
= hook.url
+
- if hook.rate_limited?
= gl_badge_tag(_('Disabled'), variant: :danger, size: :sm)
- elsif hook.permanently_disabled?
@@ -19,7 +22,10 @@
= gl_badge_tag(integration_webhook_event_human_name(trigger), size: :sm)
= gl_badge_tag(sslBadgeText, size: :sm)
- .col-md-4.col-lg-5.gl-mt-2.gl-px-5.gl-gap-3.gl-display-flex.gl-md-justify-content-end.gl-align-items-baseline
+ .col-md-2.col-lg-4.gl-px-5.gl-font-sm
+ = truncate(hook.description, length: 200)
+
+ .col-md-4.col-lg-3.gl-mt-2.gl-px-5.gl-gap-3.gl-display-flex.gl-md-justify-content-end.gl-align-items-baseline
= render 'shared/web_hooks/test_button', hook: hook, size: 'small'
= render Pajamas::ButtonComponent.new(href: edit_hook_path(hook), size: :small) do
= _('Edit')
diff --git a/config/events/schema.json b/config/events/schema.json
index dd92b36bc8e..6a32a2ab0a8 100644
--- a/config/events/schema.json
+++ b/config/events/schema.json
@@ -21,7 +21,8 @@
"type": "string"
},
"action": {
- "type": "string"
+ "type": "string",
+ "pattern": "^[a-z0-9_]+$"
},
"label_description": {
"type": [
diff --git a/db/docs/service_access_tokens.yml b/db/docs/service_access_tokens.yml
index c75b62883b0..8a0958707b0 100644
--- a/db/docs/service_access_tokens.yml
+++ b/db/docs/service_access_tokens.yml
@@ -1,7 +1,7 @@
---
table_name: service_access_tokens
classes:
-- Ai::ServiceAccessToken
+- CloudConnector::ServiceAccessToken
feature_categories:
- cloud_connector
description: Persists JWT tokens for AI features (e.g. Code Suggestions) to authenticate
diff --git a/db/migrate/20240106000000_migrate_data_from_workspaces_url_column.rb b/db/migrate/20240106000000_migrate_data_from_workspaces_url_column.rb
new file mode 100644
index 00000000000..e5f5ffc376e
--- /dev/null
+++ b/db/migrate/20240106000000_migrate_data_from_workspaces_url_column.rb
@@ -0,0 +1,59 @@
+# frozen_string_literal: true
+
+class MigrateDataFromWorkspacesUrlColumn < Gitlab::Database::Migration[2.2]
+ BATCH_SIZE = 500
+ DEFAULT_PORT = 60001
+
+ milestone '16.8'
+ disable_ddl_transaction!
+
+ restrict_gitlab_migration gitlab_schema: :gitlab_main
+
+ def up
+ each_batch_range('workspaces', scope: ->(table) { table.all }, of: BATCH_SIZE) do |min, max|
+ execute(<<~SQL)
+ UPDATE workspaces
+ SET url_prefix = CONCAT('https://#{DEFAULT_PORT}-', name),
+ dns_zone = remote_development_agent_configs.dns_zone,
+ url_query_string = CASE
+ WHEN POSITION('?' IN url) > 0
+ THEN SUBSTRING(url FROM POSITION('?' IN url) + 1)
+ ELSE ''
+ END
+ FROM
+ remote_development_agent_configs
+ WHERE
+ workspaces.cluster_agent_id = remote_development_agent_configs.cluster_agent_id
+ AND url IS NOT NULL
+ AND workspaces.id BETWEEN #{min} AND #{max}
+ SQL
+
+ execute(<<~SQL)
+ UPDATE workspaces
+ SET url = NULL
+ WHERE workspaces.id BETWEEN #{min} AND #{max}
+ AND url_prefix IS NOT NULL
+ AND dns_zone IS NOT NULL
+ AND url_query_string IS NOT NULL
+ SQL
+ end
+ end
+
+ def down
+ each_batch_range('workspaces', scope: ->(table) { table.all }, of: BATCH_SIZE) do |min, max|
+ execute(<<~SQL)
+ UPDATE workspaces
+ SET url = CONCAT(url_prefix, '.', dns_zone, '?', url_query_string)
+ WHERE workspaces.id BETWEEN #{min} AND #{max}
+ SQL
+
+ execute(<<~SQL)
+ UPDATE workspaces
+ SET url_prefix = NULL,
+ dns_zone = NULL,
+ url_query_string = NULL
+ WHERE workspaces.id BETWEEN #{min} AND #{max}
+ SQL
+ end
+ end
+end
diff --git a/db/migrate/20240116161955_add_name_and_description_to_web_hooks.rb b/db/migrate/20240116161955_add_name_and_description_to_web_hooks.rb
new file mode 100644
index 00000000000..b1360fa4aa2
--- /dev/null
+++ b/db/migrate/20240116161955_add_name_and_description_to_web_hooks.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class AddNameAndDescriptionToWebHooks < Gitlab::Database::Migration[2.2]
+ milestone '16.9'
+
+ # rubocop:disable Migration/AddLimitToTextColumns -- limit is added in
+ # db/migrate/20240116162201_add_text_limit_to_web_hooks_attributes.rb
+ def change
+ add_column :web_hooks, :name, :text
+ add_column :web_hooks, :description, :text
+ end
+ # rubocop:enable Migration/AddLimitToTextColumns
+end
diff --git a/db/migrate/20240116162201_add_text_limit_to_web_hooks_attributes.rb b/db/migrate/20240116162201_add_text_limit_to_web_hooks_attributes.rb
new file mode 100644
index 00000000000..3802f1c31f5
--- /dev/null
+++ b/db/migrate/20240116162201_add_text_limit_to_web_hooks_attributes.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddTextLimitToWebHooksAttributes < Gitlab::Database::Migration[2.2]
+ milestone '16.9'
+
+ disable_ddl_transaction!
+
+ def up
+ add_text_limit :web_hooks, :name, 255
+ add_text_limit :web_hooks, :description, 2048
+ end
+
+ def down
+ remove_text_limit :web_hooks, :name
+ remove_text_limit :web_hooks, :description
+ end
+end
diff --git a/db/schema_migrations/20240106000000 b/db/schema_migrations/20240106000000
new file mode 100644
index 00000000000..73baf388353
--- /dev/null
+++ b/db/schema_migrations/20240106000000
@@ -0,0 +1 @@
+85e9e3512a609e4c9420922dd53ca989fb0f8f70b019d9116871c5877835010c \ No newline at end of file
diff --git a/db/schema_migrations/20240116161955 b/db/schema_migrations/20240116161955
new file mode 100644
index 00000000000..2158e158e8d
--- /dev/null
+++ b/db/schema_migrations/20240116161955
@@ -0,0 +1 @@
+81b09b45a5c6b7c04cbb9fbd112a3f88f64aa837d0229c66c068d0c66efc8c83 \ No newline at end of file
diff --git a/db/schema_migrations/20240116162201 b/db/schema_migrations/20240116162201
new file mode 100644
index 00000000000..51b1472d01a
--- /dev/null
+++ b/db/schema_migrations/20240116162201
@@ -0,0 +1 @@
+82f6fe5eef8a794bbbd27919800339c03e416f85598f098eb6eb1c54252e62a3 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 763cf0dc349..803b0482055 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -25831,7 +25831,11 @@ CREATE TABLE web_hooks (
encrypted_url_variables_iv bytea,
integration_id integer,
branch_filter_strategy smallint DEFAULT 0 NOT NULL,
- emoji_events boolean DEFAULT false NOT NULL
+ emoji_events boolean DEFAULT false NOT NULL,
+ name text,
+ description text,
+ CONSTRAINT check_1e4d5cbdc5 CHECK ((char_length(name) <= 255)),
+ CONSTRAINT check_23a96ad211 CHECK ((char_length(description) <= 2048))
);
CREATE SEQUENCE web_hooks_id_seq
diff --git a/doc/architecture/blueprints/security_policies_database_read_model/index.md b/doc/architecture/blueprints/security_policies_database_read_model/index.md
new file mode 100644
index 00000000000..507191d854b
--- /dev/null
+++ b/doc/architecture/blueprints/security_policies_database_read_model/index.md
@@ -0,0 +1,256 @@
+---
+status: proposed
+creation-date: "2023-12-07"
+authors: [ "@mcavoj" ]
+coach: ""
+approvers: [ "@g.hickman", "@alan" ]
+owning-stage: ~"devops::govern"
+participating-stages: []
+---
+
+# Database read model for Security policies
+
+This document is a work in progress and represents the proposal for new Security policies architecture to allow for optimized policy updates and propagation.
+
+## Summary
+
+The security policies are stored as YAML files in the security policy project. While this approach has a lot of advantages (like version control for policies using Git, auditable etc), it faces some performance drawbacks.
+Since reading from the Git repository requires calls to Gitaly, it might block us in building flexible functionalities.
+
+## Motivation
+
+The current architecture of synchronising policies from YAML to approval rules in DB is not very performant as it involves the process of deleting and recreating the approval rules even though the event that triggers the re-sync does not need them to re-process completely (eg: user added to a project would only need to update approvers of approver rules). The current architecture faces these limitations:
+
+- [Difficult to build new functionalities](https://gitlab.com/groups/gitlab-org/-/epics/8084)
+ - Due to the limitation of reading the YAML from Gitaly everytime something gets updated, we keep a limit in the number of policies configured. But the limit is not sufficient for a lot of use-cases.
+- High resource consumption
+ - `Security::ProcessScanResultPolicyWorker` is a long-running worker, it makes call to Gitaly, deletes and create approval rules, updates all open MRs for the project. If the project has a huge number of MRs, it could take few minutes to complete, as we currently don't have the possibility to selectively sync a policy.
+- Less fault tolerant
+ - Since all the operations performed by the worker has to be atomic, if one step fails, it could make the final state of the system to be inconsistent
+- Redundant data stored in approval rules table and `scan_result_policies`
+ - Since we store fields from YAML into approval rules table for all MRs, they are redundant and consume a lot of extra disk space
+ - Since we store `project_id` in `scan_result_policies`, we have a huge number of records for a low number of policies that are actually in security policy repository
+- Difficulties of [fetching the security policies list due to access management](https://gitlab.com/gitlab-org/gitlab/-/issues/432141)
+ - Users currently need access to security policy project in order to see the policies applicable to a project where they already have access. This would be solved if we read the policies from the database.
+
+### Goals
+
+1. Reduce the calls to Gitaly and depend on reading from the database. The YAML data will be mirrored on a database table and a read-only ActiveRecord model allows us to build features without performance concerns.
+1. Remove duplicated columns related to scan result policies in approval_project_rules and approval_merge_request_rules
+1. Change the current process in [UpdateOrchestrationPolicyConfiguration](https://gitlab.com/gitlab-org/gitlab/-/blob/master/ee/app/workers/concerns/update_orchestration_policy_configuration.rb) not to delete and recreate all related records, but rather update/recreate only the affected records
+1. Reduce the average propagation duration of scan result policy changes
+1. Reduce the number of queries to the database performed by workers from Security Policies
+
+## Proposal
+
+### Current architecture of Security policies
+
+Security policy project that are linked to a group or a project has a corresponding entry in `security_orchestration_policy_configurations` table. In order to read/query policies, the YAML has to be read from policy project which involves a RPC call to Gitaly. This does not perform well when there are huge number of projects/groups configured with security policies.
+
+Scan result policy that are stored in the policy project as YAML file gets synchronised to approval rules through `approval_project_rules` table. Currently we are storing these fields related to scan result policy in the table:
+
+- `scanners`
+- `vulnerabilities_allowed`
+- `severity_levels`
+- `report_type`
+- `vulnerability_states`
+
+The same fields are also duplicated in `approval_merge_request_rules` to relate them to each individual merge request. They are redundant as any value for these fields changed in `approval_project_rules` results in `approval_merge_request_rules` getting updated too.
+
+`Security::ProcessScanResultPolicyWorker` is responsible for reading the policy from policy project and transforming them to approval rules. The worker performs these operations in sequence:
+
+- Delete `approval_project_rules` and `approval_merge_request_rules`
+- Read YAML from policy project (call to Gitaly)
+- Convert the YAML to create rows in `approval_project_rules`
+- Update each of `approval_merge_request_rules` for open merge requests
+
+We delete and recreate approval rules because we cannot get the exact diff of the policy from YAML very efficiently as it involves call to Gitaly and also we do not have unique identifier for each policy in the YAML.
+
+This worker is called whenever these events happen:
+
+- Project created within a group with policies configured
+- User added/removed from a project
+- Protected branch is added/removed from a project
+- Policy is created/updated/deleted for a project
+
+To avoid this, we have created the `scan_result_policies` table (`Security::ScanResultPolicyRead` model) which acts as a read model for scan result policies to avoid reading from policy project. But currently, we don't store all the required fields in the table, we only store `role_approvers` , `license_state` and `match_on_inclusion`.
+
+```mermaid
+erDiagram
+ security_orchestration_policy_configurations ||--o{ scan_result_policies : " "
+ security_orchestration_policy_configurations ||--o{ approval_project_rules : " "
+ security_orchestration_policy_configurations ||--o{ approval_merge_request_rules : " "
+ scan_result_policies ||--|| approval_project_rules : " "
+ scan_result_policies ||--|| approval_merge_request_rules : " "
+
+ security_orchestration_policy_configurations {
+ int project_id
+ int namespace_id
+ string security_policy_management_project_id
+ }
+ scan_result_policies {
+ int security_orchestration_policy_configuration_id
+ int orchestration_policy_idx
+ text license_states
+ boolean match_on_inclusion
+ int role_approvers
+ int age_value
+ smallint age_operator
+ smallint age_interval
+ jsonb vulnerability_attributes
+ int project_id
+ jsonb project_approval_settings
+ smallint commits
+ }
+ approval_project_rules {
+ int scan_result_policy_id
+ int security_orchestration_policy_configuration_id
+ int orchestration_policy_idx
+ text scanners
+ smallint vulnerabilities_allowed
+ text severity_levels
+ smallint report_type
+ text vulnerability_states
+ }
+ approval_merge_request_rules {
+ int merge_request_id
+ int scan_result_policy_id
+ int security_orchestration_policy_configuration_id
+ int orchestration_policy_idx
+ text scanners
+ smallint vulnerabilities_allowed
+ text severity_levels
+ smallint report_type
+ text vulnerability_states
+ }
+```
+
+### Proposed Architecture
+
+To solve the challenges and limitations mentioned above, we need to persist all fields from the policy YAML to DB (in `security_policies` table) which can be used instead of reading from YAML in Git repository.
+
+The DB schema should closely mimic the policy YAML and should look like this:
+
+```mermaid
+erDiagram
+ security_orchestration_policy_configurations ||--|{ security_policies : " "
+ security_policies ||--o{ scan_execution_policy_rules : " "
+ security_policies ||--o{ scan_result_policy_rules : " "
+ security_policies }o--o{ projects : "via security_policies_projects"
+ scan_result_policy_rules ||--|| approval_group_rules : " "
+ scan_result_policy_rules ||--|| approval_project_rules : " "
+ scan_result_policy_rules ||--|| approval_merge_request_rules : " "
+ scan_result_policy_rules ||--|| software_license_policies : " "
+ scan_result_policy_rules ||--|| scan_result_policy_violations : " "
+
+ security_orchestration_policy_configurations {
+ int project_id
+ int namespace_id
+ int security_policy_management_project_id
+ }
+ security_policies {
+ int security_orchestration_policy_configuration_id
+ int policy_index
+ text checksum
+ text name
+ text type
+ text description
+ boolean enabled
+ jsonb policy_scope
+ jsonb actions
+ jsonb approval_settings
+ }
+ projects {
+ int id
+ text name
+ }
+ scan_execution_policy_rules {
+ int security_policy_id
+ int rule_index
+ text checksum
+ jsonb content
+ }
+ scan_result_policy_rules {
+ int security_policy_id
+ int rule_index
+ int type
+ text checksum
+ jsonb content
+ }
+
+ approval_group_rules {
+ int namespace_id
+ int scan_result_policy_rule_id
+ }
+ approval_project_rules {
+ int project_id
+ int scan_result_policy_rule_id
+ }
+ approval_merge_request_rules {
+ int merge_request_id
+ int scan_result_policy_rule_id
+ }
+ software_license_policies {
+ int project_id
+ int scan_result_policy_rule_id
+ }
+ scan_result_policy_violations {
+ int project_id
+ int merge_request_id
+ int scan_result_policy_rule_id
+ }
+```
+
+In order to achieve this, we want to introduce a new worker(`Security::ScanResultPolicies::SyncWorker`) that reads the YAML from Git repository and convert them to entries in `security_policies` table, together with the underlying `scan_execution_policy_rules` and `scan_result_policy_rules` tables. This worker should be called only when a policy is created/updated/deleted. In all the other places where we currently read the YAML from Git repository, we should read from `security_policies` which serves as SSoT for the latest version of the policies.
+
+This allows us to:
+
+- Know what updated in a policy by comparing the updated value in YAML from values in the table
+- Improve performance by adding DB index
+- Reduce redundant data stored in `approval_merge_request_rules` and `approval_project_rules` tables, thereby reducing the DB size
+- Dramatically reduce the number of `scan_result_policies` rows by removing the `project_id` column and creating the link between the projects via a join table
+- Reduce redundant data stored in `scan_result_policies`, as we the rule data would be decomposed and we wouldn't have duplicated `project_approval_settings`, thereby reducing the DB size
+
+There is an ongoing effort in [Allow group-level MR approval rules for 'All Protected Branches'](https://gitlab.com/groups/gitlab-org/-/epics/11451) that would allow us to define `approval_group_rules` for group policies. This means we will not need to copy `approval_project_rules` for every project in a group, reducing the number of workers needed to propagate policy changes.
+
+## Design and Implementation Details
+
+### Step 1: Add new tables
+
+As a first step, we need to introduce `security_policies`, `scan_result_policy_rules` and `scan_execution_policy_rules` tables. These tables and columns map to the fields in YAML.
+
+### Step 2: Introduce new worker to sync policies to the DB tables
+
+The new worker would be responsible for reading and converting the YAML to rows in `security_policies` table.
+Using `checksum`, we can determine whether a policy has changed and requires a rebuild, or whether it was only re-ordered.
+
+We can compare changes and based on the columns that were updated, we can propagate policy changes. For example:
+
+- When `actions` are updated, we can update the approvers in the approval rules.
+- When `rules` are updated, we can trigger update of the approval rules associated to them.
+
+### Step 3: Migrate all existing policies to `security_policies` table
+
+We need to introduce DB migration that reads all existing policies and populate `security_policies` table.
+
+### Step 4: Update `Security::ProcessScanResultPolicyWorker` to read from `security_policies`
+
+Modify the worker to read from `security_policies` and invoke it only for these events:
+
+- User added/removed from a project
+- Protected branch is added/removed from a project
+
+This would change the worker's responsibility to only update the approval rules associated to the `security_policies` based on the event to which it was triggered
+
+### Step 5: Delete columns from approval rules table
+
+This step would delete the columns that are migrated to `security_policies` from `approval_project_rules` and `approval_merge_request_rules`.
+
+### Step 6: Remove `scan_result_policies` table
+
+At this point, we can remove the old table `scan_result_policies` because the approval rules would be linked via `scan_result_policy_rules` table.
+
+## Links
+
+- [Use database read model for scan result policies](https://gitlab.com/groups/gitlab-org/-/epics/9971)
+- [Spike: Prepare architecture blueprint for database read model for scan result policies](https://gitlab.com/gitlab-org/gitlab/-/issues/433410)
diff --git a/doc/architecture/blueprints/security_policy_custom_ci/index.md b/doc/architecture/blueprints/security_policy_custom_ci/index.md
new file mode 100644
index 00000000000..190e9c60973
--- /dev/null
+++ b/doc/architecture/blueprints/security_policy_custom_ci/index.md
@@ -0,0 +1,127 @@
+---
+status: ongoing
+creation-date: "2023-11-23"
+authors: [ "@lohrc", "@g.hickman" ]
+coach: "@fabiopitino"
+approvers: [ "@g.hickman" ]
+owning-stage: ~"devops::govern"
+participating-stages: ["~devops::verify"]
+---
+
+# Pipeline Execution Policy
+
+This document is a work in progress and represents the current state of the vision to allow users to enforce CI jobs to be run as part of project pipelines and enabling users to link/scope those jobs to projecs using security policies and compliance frameworks.
+
+## Summary
+
+Users need a single solution for enforcing jobs to be run as part of a project pipeline. They want a way to combine the flexibility of [compliance framework pipelines](../../../user/group/compliance_frameworks.md#compliance-pipelines) with the simplicity of [scan execution policies](../../../user/application_security/policies/scan-execution-policies.md#scan-execution-policies-schema).
+
+There are many cases that could be addressed using pipeline execution policies to define policy rules, but here are a few of the most common we've heard so far:
+
+1. I want to include CI templates using `include` statements and be able to pull in templates such as `.latest` security scanner templates. See [CI templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates).
+1. I want to enforce execution of a 3rd party scanner, so that I can capture additional security results and manage them alongside results from GitLab scanners in the vulnerability report.
+1. I want to enforce a custom script/check for compliance.
+1. I want to enforce scanners to analyze code stored in GitLab, but call out to a 3rd party CI tool to continue a pipeline. A common case here may be for users migrating to GitLab, but some teams still may be using 3rd party CI tools in the meantime.
+1. I want to export results from scanners to a 3rd party tool.
+1. I want to block MRs until a UAT in an external tool is completed (quality gate), so that I can ensure the quality of changes to my production code.
+1. I want to standardize custom project badges that can quickly communicate to users in a project which security policies are enabled and if pipelines are blocked by a given scan result, so that my security team and developer team can easily understand the security state of a project.
+1. I want to create a custom badge in projects to communicate the security grade, so that team members are incentivized to keep their score high.
+
+## Motivation
+
+### Goals
+
+1. Provide a proper replacement for compliance pipelines, with an objective of deprecating compliance pipelines in 16.9 and removal in 17.0.
+1. Set up the architecture in a way that the feature can be enhanced to solve known compliance pipelines issues and customer pain points for enforcing scan execution globally across their organization.
+
+Known compliance pipelines issues are:
+
+- [Unable to support a project with an external config file or no config file](https://gitlab.com/gitlab-org/gitlab/-/issues/416104#1-unable-to-support-a-project-with-an-external-config-file-or-no-config-file)
+- [CI variables in the Compliance config can be overwritten](https://gitlab.com/gitlab-org/gitlab/-/issues/416104#2-ci-variables-in-the-compliance-config-can-be-overwritten)
+- [Compliance jobs can be overwritten by target repository](https://gitlab.com/gitlab-org/gitlab/-/issues/416104#3-compliance-jobs-can-be-overwritten-by-target-repository)
+- [Conflict with top-level global keywords](https://gitlab.com/gitlab-org/gitlab/-/issues/416104#4-conflict-with-top-level-global-keywords-eg-stages)
+- [Must ensure compliance jobs are always run](https://gitlab.com/gitlab-org/gitlab/-/issues/416104#5-must-ensure-compliance-jobs-are-always-run)
+- [Prefilled variables are not shown when manually starting pipeline](https://gitlab.com/gitlab-org/gitlab/-/issues/416104#6-prefilled-variables-are-not-shown-when-manually-starting-pipeline)
+- [Need to unify Scan Execution Policies and Compliance Framework](https://gitlab.com/gitlab-org/gitlab/-/issues/416104#7-need-to-unify-scan-execution-policies-and-compliance-framework)
+- [Compliance Framework not found or access denied](https://gitlab.com/gitlab-org/gitlab/-/issues/404707)
+- [Compliance pipeline jobs don't run in a specific situation due to GitLab only checking the project's pipeline configuration](https://gitlab.com/gitlab-org/gitlab/-/issues/412279)
+- [.pre/.post jobs should be optionally allowed in empty pipelines](https://gitlab.com/gitlab-org/gitlab/-/issues/420339)
+- [Allow compliance pipelines to hold constant any values that are set from an include file](https://gitlab.com/gitlab-org/gitlab/-/issues/365381)
+- [Run Compliance Framework pipeline in MRs that originate in forks of the CF-labeled project](https://gitlab.com/gitlab-org/gitlab/-/issues/374099)
+- [Compliance pipeline configuration in not exposed in merged_YAML](https://gitlab.com/gitlab-org/gitlab/-/issues/367247)
+
+## Proposal
+
+Currently, security policies can include multiple scan actions. Each scan action will result in a CI job that will be injected in the project CI pipeline.
+The new feature allows you to define custom CI jobs that will be injected into the project CI pipeline as well. We want to generalize the security policy
+approach to provide the same flexibility that [compliance framework](../../../user/group/compliance_frameworks.md) needs. The combination of the 2 features
+means that security policies can be scope to compliance frameworks and enforce the presence of custom CI jobs.
+
+Like scan execution policies, custom CI jobs can be scoped to certain branch names, branch types or compliance frameworks applied to the project.
+Users will be able to define custom stages and be able to place jobs to run before or after certain CI stages of the project pipeline.
+Transitioning from compliance pipelines to the new feature should be as smooth as possible.
+
+## Design and Implementation Details
+
+### Pipeline Execution Policy MVC
+
+The Pipeline Execution Policy MVC will allow the transition from compliance pipelines.
+
+- It should be possible to add custom CI YAML to a security policy. The CI YAML should follow the same schema as `.gitlab-ci.yml` files. The custom CI will be merged with the project CI when a pipeline starts.
+- The security policy schema should allow the custom CI to be defined in an external file by allowing a project and file path to be added.
+- Scan execution policies should execute custom CI YAML similar to existing policies, by injecting the job into the GitLab CI YAML of the target projects.
+- At minimum, pipeline execution policy jobs should align with [existing CI variable precedence](../../../ci/variables/index.md#cicd-variable-precedence). Ideally, all CI variables defined in a scan execution policy job should execute as highest precedence. Specifically, scan execution job variables should take precedence over project, group, and instance variables compliance project variables, among others.
+- Jobs should be executed in a way that is visible to users within the pipeline and that will not allow project jobs to override the SEP jobs. In scan execution policies today, we utilize the index pattern (-0,-1,-2,...) to increment the name of the job if a job of the same name exists. This also gives some minor indication of which jobs are executed by a security policy. For custom YAML jobs, the same pattern should be utilized.
+- Users should be able to define the stage in which their job will run, and scan execution policies will have a method to handle precedence. For example, a security/compliance team may define want to enforce jobs that run commonly after a build stage. They would be able to use build_after (for example) and scan execution policies would inject the build_after stage after the build stage and enforce the custom CI YAML defined in the pipeline execution policy within this stage. The stage and job cannot be interfered with by development teams once enforced by a scan execution policy. We should define the rules that allow for injecting stages cleanly into all enforced projects but be minimal invasive as to the project CI.
+- Pipeline execution policies should execute custom CI YAML in projects that do not contain an existing CI configuration, the same as standard scan execution policies work today.
+
+### Stages definitions
+
+We want users to be able to place jobs to run before or after certain CI stages of the project pipeline. But we haven't decided about a strategy to archive this.
+We discussed the following strategies:
+
+#### 1. Make security policy stages order take precedence
+
+Pipeline execution policies can modify stages by redefining them using the `stages` keyword.
+The order of stages defined in the pipeline execution policy will take precedence over the order defined in the project CI.
+
+If a pipeline execution policy wants to inject a job after the `test` stage, it can redefine stages as:
+
+```yaml
+stages:
+ - test
+ - policy_after_test
+```
+
+This solution provides flexibility to users as they can define custom stages. The downside is that there will be no single source of truth for the order of stages anymore.
+If a project CI defines a custom stage that is not defined in the pipeline execution policy, we don't know if it should run before or after the pipeline execution policy stages.
+
+#### 2. Introduce `pre` and `post` keywords
+
+Security policies can use `pre_[stage_name]` and `post_[stage_name]` stages to inject jobs before or after certain stages. For example, `pre_test` would run before the `test` stage.
+This way the security policy are unlikely to disrupt the project CI.
+The downside is that the policy author needs to be aware of the stages used by projects and policy stages can be skipped by renaming the `test` stage to `test_2` for example.
+
+#### 3. Introduce advanced stage rules
+
+An advanced API for pipeline execution policy stages allows to inject stages depending on the existence of other stages. Schema example:
+
+```yaml
+compliance_stages:
+- stage_name: qa
+ after:
+ - release
+ if:
+ - stage_exist: deploy
+```
+
+This will provide flexibility and allows users to define the behavior for different project CI stage setups.
+
+#### 4. Run all jobs in a default stage
+
+Jobs defined in a pipeline execution policy will run in the `pipeline-policy` stage. This stage will be injected after the `test` stage.
+If the `test` stage doesn't exist, it will be injected after the `build` stage. If the `build` stage doesn't exist, it will be injected at the beginning of the pipeline.
+
+## Links
+
+- [Pipeline execution policy MVC epic](https://gitlab.com/groups/gitlab-org/-/epics/7312)
diff --git a/doc/development/ai_features/index.md b/doc/development/ai_features/index.md
index 0593384112a..8f9ffa20fe7 100644
--- a/doc/development/ai_features/index.md
+++ b/doc/development/ai_features/index.md
@@ -215,7 +215,7 @@ Therefore, a different setup is required from the [SaaS-only AI features](#test-
```ruby
# Creating dummy token, and this will work as long as `AIGW_AUTH__BYPASS_EXTERNAL=true` in AI Gateway.
- ::Ai::ServiceAccessToken.create!(token: 'dummy', expires_at: 1.month.from_now)
+ ::CloudConnector::ServiceAccessToken.create!(token: 'dummy', expires_at: 1.month.from_now)
```
1. Ensure GitLab-Rails can talk to the AI Gateway. Run `gdk rails console` and execute:
diff --git a/doc/development/internal_analytics/internal_event_instrumentation/event_definition_guide.md b/doc/development/internal_analytics/internal_event_instrumentation/event_definition_guide.md
index 9417861eee9..e00c0540c16 100644
--- a/doc/development/internal_analytics/internal_event_instrumentation/event_definition_guide.md
+++ b/doc/development/internal_analytics/internal_event_instrumentation/event_definition_guide.md
@@ -26,7 +26,7 @@ Each event is defined in a separate YAML file consisting of the following fields
|------------------------|----------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `description` | yes | A description of the event. |
| `category` | yes | Always InternalEventTracking (only different for legacy events). |
-| `action` | yes | A unique name for the event. Use the format `<operation>_<target_of_operation>_<where/when>`. <br/><br/> Ex: `publish_go_module_to_the_registry_from_pipeline` <br/>`<operation> = publish`<br/>`<target> = go_module`<br/>`<when/where> = to_the_registry_from_pipeline`. |
++| `action` | yes | A unique name for the event. Only lowercase, numbers, and underscores are allowed. Use the format `<operation>_<target_of_operation>_<where/when>`. <br/><br/> Ex: `publish_go_module_to_the_registry_from_pipeline` <br/>`<operation> = publish`<br/>`<target> = go_module`<br/>`<when/where> = to_the_registry_from_pipeline`. |
| `identifiers` | no | A list of identifiers sent with the event. Can be set to one or more of `project`, `user`, or `namespace`. |
| `product_section` | yes | The [section](https://gitlab.com/gitlab-com/www-gitlab-com/-/blob/master/data/sections.yml). |
| `product_stage` | no | The [stage](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml) for the event. |
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 79c9e718590..2b9cc4a1470 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -55142,6 +55142,9 @@ msgstr ""
msgid "Webhooks|Deployment events"
msgstr ""
+msgid "Webhooks|Description (optional)"
+msgstr ""
+
msgid "Webhooks|Do not show sensitive data such as tokens in the UI."
msgstr ""
@@ -55181,6 +55184,9 @@ msgstr ""
msgid "Webhooks|Must match part of URL"
msgstr ""
+msgid "Webhooks|Name (optional)"
+msgstr ""
+
msgid "Webhooks|Pipeline events"
msgstr ""
@@ -58299,6 +58305,9 @@ msgstr ""
msgid "for Workspace must have an associated RemoteDevelopmentAgentConfig"
msgstr ""
+msgid "for Workspace must match the dns_zone of the associated RemoteDevelopmentAgentConfig"
+msgstr ""
+
msgid "for this project"
msgstr ""
@@ -58446,9 +58455,6 @@ msgstr ""
msgid "is blocked by"
msgstr ""
-msgid "is currently immutable, and cannot be updated. Create a new agent instead."
-msgstr ""
-
msgid "is forbidden by a top-level group"
msgstr ""
diff --git a/scripts/internal_events/cli/event_definer.rb b/scripts/internal_events/cli/event_definer.rb
index e029f0e7cf6..54f81a62dd1 100755
--- a/scripts/internal_events/cli/event_definer.rb
+++ b/scripts/internal_events/cli/event_definer.rb
@@ -71,9 +71,9 @@ module InternalEventsCli
event.action = cli.ask("Define the event name: #{input_required_text}", **input_opts) do |q|
q.required true
- q.validate ->(input) { input =~ /\A\w+\z/ && !events_by_filepath.values.map(&:action).include?(input) } # rubocop:disable Rails/NegateInclude -- Not rails
+ q.validate ->(input) { input =~ /\A[a-z1-9_]+\z/ && !events_by_filepath.values.map(&:action).include?(input) } # rubocop:disable Rails/NegateInclude -- Not rails
q.modify :trim
- q.messages[:valid?] = format_warning("Invalid event name. Only letters/numbers/underscores allowed. " \
+ q.messages[:valid?] = format_warning("Invalid event name. Only lowercase/numbers/underscores allowed. " \
"Ensure %{value} is not an existing event.")
q.messages[:required?] = Text::EVENT_ACTION_HELP
end
diff --git a/spec/factories/ai/service_access_tokens.rb b/spec/factories/ai/service_access_tokens.rb
index 0598eed52c4..307b87dc8c8 100644
--- a/spec/factories/ai/service_access_tokens.rb
+++ b/spec/factories/ai/service_access_tokens.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
FactoryBot.define do
- factory :service_access_token, class: 'Ai::ServiceAccessToken' do
+ factory :service_access_token, class: 'CloudConnector::ServiceAccessToken' do
token { SecureRandom.alphanumeric(10) }
expires_at { Time.current + 1.day }
diff --git a/spec/features/dashboard/groups_list_spec.rb b/spec/features/dashboard/groups_list_spec.rb
index 745e45478d1..059c39e193d 100644
--- a/spec/features/dashboard/groups_list_spec.rb
+++ b/spec/features/dashboard/groups_list_spec.rb
@@ -10,7 +10,7 @@ RSpec.describe 'Dashboard Groups page', :js, feature_category: :groups_and_proje
def click_group_caret(group)
within("#group-#{group.id}") do
- first('.folder-caret').click
+ find_by_testid('group-item-toggle-button').click
end
wait_for_requests
end
diff --git a/spec/frontend/groups/components/group_item_spec.js b/spec/frontend/groups/components/group_item_spec.js
index 26c97a7cb41..5b96ecb4750 100644
--- a/spec/frontend/groups/components/group_item_spec.js
+++ b/spec/frontend/groups/components/group_item_spec.js
@@ -269,7 +269,6 @@ describe('GroupItemComponent', () => {
expect(vm.$el.querySelector('.group-row-contents .stats')).toBeDefined();
expect(vm.$el.querySelector('.folder-toggle-wrap')).toBeDefined();
- expect(vm.$el.querySelector('.folder-toggle-wrap .folder-caret')).toBeDefined();
expect(vm.$el.querySelector('.folder-toggle-wrap .item-type-icon')).toBeDefined();
expect(vm.$el.querySelector('.avatar-container')).toBeDefined();
diff --git a/spec/frontend/groups/components/item_caret_spec.js b/spec/frontend/groups/components/item_caret_spec.js
deleted file mode 100644
index ff273fcf6da..00000000000
--- a/spec/frontend/groups/components/item_caret_spec.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import { GlIcon } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import ItemCaret from '~/groups/components/item_caret.vue';
-
-describe('ItemCaret', () => {
- let wrapper;
-
- const defaultProps = {
- isGroupOpen: false,
- };
-
- const createComponent = (props = {}) => {
- wrapper = shallowMount(ItemCaret, {
- propsData: { ...defaultProps, ...props },
- });
- };
-
- const findAllGlIcons = () => wrapper.findAllComponents(GlIcon);
- const findGlIcon = () => wrapper.findComponent(GlIcon);
-
- describe('template', () => {
- it('renders component template correctly', () => {
- createComponent();
-
- expect(wrapper.classes()).toContain('folder-caret');
- expect(findAllGlIcons()).toHaveLength(1);
- });
-
- it.each`
- isGroupOpen | icon
- ${true} | ${'chevron-down'}
- ${false} | ${'chevron-right'}
- `('renders "$icon" icon when `isGroupOpen` is $isGroupOpen', ({ isGroupOpen, icon }) => {
- createComponent({
- isGroupOpen,
- });
-
- expect(findGlIcon().props('name')).toBe(icon);
- });
- });
-});
diff --git a/spec/models/ai/service_access_token_spec.rb b/spec/models/cloud_connector/service_access_token_spec.rb
index d491735e604..4239ec486a5 100644
--- a/spec/models/ai/service_access_token_spec.rb
+++ b/spec/models/cloud_connector/service_access_token_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Ai::ServiceAccessToken, type: :model, feature_category: :cloud_connector do
+RSpec.describe CloudConnector::ServiceAccessToken, type: :model, feature_category: :cloud_connector do
describe '.expired', :freeze_time do
let_it_be(:expired_token) { create(:service_access_token, :expired) }
let_it_be(:active_token) { create(:service_access_token, :active) }
@@ -22,16 +22,18 @@ RSpec.describe Ai::ServiceAccessToken, type: :model, feature_category: :cloud_co
end
describe '#token' do
+ subject(:service_access_token) { described_class.new }
+
let(:token_value) { 'Abc' }
it 'is encrypted' do
- subject.token = token_value
+ service_access_token.token = token_value
aggregate_failures do
- expect(subject.encrypted_token_iv).to be_present
- expect(subject.encrypted_token).to be_present
- expect(subject.encrypted_token).not_to eq(token_value)
- expect(subject.token).to eq(token_value)
+ expect(service_access_token.encrypted_token_iv).to be_present
+ expect(service_access_token.encrypted_token).to be_present
+ expect(service_access_token.encrypted_token).not_to eq(token_value)
+ expect(service_access_token.token).to eq(token_value)
end
end
diff --git a/spec/scripts/internal_events/cli_spec.rb b/spec/scripts/internal_events/cli_spec.rb
index 54169e0dc2d..30d4db7d195 100644
--- a/spec/scripts/internal_events/cli_spec.rb
+++ b/spec/scripts/internal_events/cli_spec.rb
@@ -89,6 +89,18 @@ RSpec.describe Cli, feature_category: :service_ping 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
+
+ context 'with invalid event name' do
+ it 'prompts user to select another name' do
+ queue_cli_inputs([
+ "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
+ "badDDD_ event (name) with // prob.lems\n" # Submit action name
+ ])
+
+ expect_cli_output { prompt.output.string.include?('Invalid event name.') }
+ end
+ end
end
context 'when creating new metrics' do