Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-01-05 12:09:59 +00:00
parent f9053931de
commit b90cf01a88
73 changed files with 609 additions and 344 deletions

View File

@ -69,7 +69,7 @@ export default {
isCollapsible() {
if (!this.isLoadingSummary && this.loadingState !== LOADING_STATES.collapsedError) {
if (this.shouldCollapse) {
return this.shouldCollapse();
return this.shouldCollapse(this.collapsedData);
}
return true;

View File

@ -173,6 +173,9 @@ export default {
childrenTypeName() {
return WORK_ITEMS_TYPE_MAP[this.childrenType]?.name;
},
childrenTypeValue() {
return WORK_ITEMS_TYPE_MAP[this.childrenType]?.value;
},
addOrCreateButtonLabel() {
if (this.isCreateForm) {
return sprintfWorkItem(I18N_WORK_ITEM_CREATE_BUTTON_LABEL, this.childrenTypeName);
@ -198,7 +201,7 @@ export default {
return this.isCreateForm ? this.createChild : this.addChild;
},
childWorkItemType() {
return this.workItemTypes.find((type) => type.name === this.childrenTypeName)?.id;
return this.workItemTypes.find((type) => type.name === this.childrenTypeValue)?.id;
},
parentIterationId() {
return this.parentIteration?.id;

View File

@ -31,7 +31,12 @@ export const WORK_ITEM_TYPE_ENUM_REQUIREMENTS = 'REQUIREMENTS';
export const WORK_ITEM_TYPE_ENUM_OBJECTIVE = 'OBJECTIVE';
export const WORK_ITEM_TYPE_ENUM_KEY_RESULT = 'KEY_RESULT';
export const WORK_ITEM_TYPE_VALUE_INCIDENT = 'Incident';
export const WORK_ITEM_TYPE_VALUE_ISSUE = 'Issue';
export const WORK_ITEM_TYPE_VALUE_TASK = 'Task';
export const WORK_ITEM_TYPE_VALUE_TEST_CASE = 'Test case';
export const WORK_ITEM_TYPE_VALUE_REQUIREMENTS = 'Requirements';
export const WORK_ITEM_TYPE_VALUE_KEY_RESULT = 'Key result';
export const WORK_ITEM_TYPE_VALUE_OBJECTIVE = 'Objective';
export const i18n = {
@ -103,30 +108,37 @@ export const WORK_ITEMS_TYPE_MAP = {
[WORK_ITEM_TYPE_ENUM_INCIDENT]: {
icon: `issue-type-incident`,
name: s__('WorkItem|Incident'),
value: WORK_ITEM_TYPE_VALUE_INCIDENT,
},
[WORK_ITEM_TYPE_ENUM_ISSUE]: {
icon: `issue-type-issue`,
name: s__('WorkItem|Issue'),
value: WORK_ITEM_TYPE_VALUE_ISSUE,
},
[WORK_ITEM_TYPE_ENUM_TASK]: {
icon: `issue-type-task`,
name: s__('WorkItem|Task'),
value: WORK_ITEM_TYPE_VALUE_TASK,
},
[WORK_ITEM_TYPE_ENUM_TEST_CASE]: {
icon: `issue-type-test-case`,
name: s__('WorkItem|Test case'),
value: WORK_ITEM_TYPE_VALUE_TEST_CASE,
},
[WORK_ITEM_TYPE_ENUM_REQUIREMENTS]: {
icon: `issue-type-requirements`,
name: s__('WorkItem|Requirements'),
value: WORK_ITEM_TYPE_VALUE_REQUIREMENTS,
},
[WORK_ITEM_TYPE_ENUM_OBJECTIVE]: {
icon: `issue-type-objective`,
name: s__('WorkItem|Objective'),
value: WORK_ITEM_TYPE_VALUE_OBJECTIVE,
},
[WORK_ITEM_TYPE_ENUM_KEY_RESULT]: {
icon: `issue-type-keyresult`,
name: s__('WorkItem|Key Result'),
value: WORK_ITEM_TYPE_VALUE_KEY_RESULT,
},
};

View File

@ -41,6 +41,7 @@ module Integrations
:external_wiki_url,
:google_iap_service_account_json,
:google_iap_audience_client_id,
:incident_events,
:inherit_from_id,
# We're using `issues_events` and `merge_requests_events`
# in the view so we still need to explicitly state them

View File

@ -6,11 +6,12 @@ class BranchesFinder < GitRefsFinder
end
def execute(gitaly_pagination: false)
if gitaly_pagination && names.blank? && search.blank?
if gitaly_pagination && names.blank? && search.blank? && regex.blank?
repository.branches_sorted_by(sort, pagination_params)
else
branches = repository.branches_sorted_by(sort)
branches = by_search(branches)
branches = by_regex(branches)
by_names(branches)
end
end
@ -29,6 +30,11 @@ class BranchesFinder < GitRefsFinder
@params[:per_page].presence
end
def regex
@params[:regex].to_s.presence
end
strong_memoize_attr :regex
def page_token
"#{Gitlab::Git::BRANCH_REF_PREFIX}#{@params[:page_token]}" if @params[:page_token]
end
@ -45,4 +51,14 @@ class BranchesFinder < GitRefsFinder
branch_names.include?(branch.name)
end
end
def by_regex(branches)
return branches unless regex
branch_filter = ::Gitlab::UntrustedRegexp.new(regex)
branches.select do |branch|
branch_filter.match?(branch.name)
end
end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
module IntegrationsHelper
# rubocop:disable Metrics/CyclomaticComplexity
def integration_event_title(event)
case event
when "push", "push_events"
@ -27,8 +28,11 @@ module IntegrationsHelper
_("Deployment")
when "alert"
_("Alert")
when "incident"
_("Incident")
end
end
# rubocop:enable Metrics/CyclomaticComplexity
def integration_event_description(integration, event)
case integration
@ -230,6 +234,7 @@ module IntegrationsHelper
end
end
# rubocop:disable Metrics/CyclomaticComplexity
def default_integration_event_description(event)
case event
when "push", "push_events"
@ -256,8 +261,11 @@ module IntegrationsHelper
s_("ProjectService|Trigger event when a deployment starts or finishes.")
when "alert"
s_("ProjectService|Trigger event when a new, unique alert is recorded.")
when "incident"
s_("ProjectService|Trigger event when an incident is created.")
end
end
# rubocop:enable Metrics/CyclomaticComplexity
def trigger_events_for_integration(integration)
Integrations::EventSerializer.new(integration: integration).represent(integration.configurable_events).to_json

View File

@ -83,14 +83,15 @@ module Emails
end
end
def access_token_expired_email(user)
def access_token_expired_email(user, token_names = [])
return unless user && user.active?
@user = user
@token_names = token_names
@target_url = profile_personal_access_tokens_url
Gitlab::I18n.with_locale(@user.preferred_language) do
mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("Your personal access token has expired")))
mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("Your personal access tokens have expired")))
end
end

View File

@ -2,8 +2,7 @@
class Analytics::CycleAnalytics::Aggregation < ApplicationRecord
include FromUnion
belongs_to :group, optional: false
include Analytics::CycleAnalytics::Parentable
validates :incremental_runtimes_in_seconds, :incremental_processed_records, :full_runtimes_in_seconds, :full_processed_records, presence: true, length: { maximum: 10 }, allow_blank: true
@ -58,7 +57,10 @@ class Analytics::CycleAnalytics::Aggregation < ApplicationRecord
estimation < 1 ? nil : estimation.from_now
end
def self.safe_create_for_group(group)
def self.safe_create_for_namespace(group_or_project_namespace)
# Namespaces::ProjectNamespace has no root_ancestor
# Related: https://gitlab.com/gitlab-org/gitlab/-/issues/386124
group = group_or_project_namespace.is_a?(Group) ? group_or_project_namespace : group_or_project_namespace.parent
top_level_group = group.root_ancestor
aggregation = find_by(group_id: top_level_group.id)
return aggregation if aggregation.present?

View File

@ -5,8 +5,7 @@ module Analytics
class ProjectStage < ApplicationRecord
include Analytics::CycleAnalytics::Stage
validates :project, presence: true
belongs_to :project
belongs_to :project, optional: false
belongs_to :value_stream, class_name: 'Analytics::CycleAnalytics::ProjectValueStream', foreign_key: :project_value_stream_id
alias_attribute :parent, :project

View File

@ -281,7 +281,7 @@ module Ci
return [] unless forward_yaml_variables?
yaml_variables.to_a.map do |hash|
if hash[:raw] && ci_raw_variables_in_yaml_config_enabled?
if hash[:raw]
{ key: hash[:key], value: hash[:value], raw: true }
else
{ key: hash[:key], value: ::ExpandVariables.expand(hash[:value], expand_variables) }
@ -293,7 +293,7 @@ module Ci
return [] unless forward_pipeline_variables?
pipeline.variables.to_a.map do |variable|
if variable.raw? && ci_raw_variables_in_yaml_config_enabled?
if variable.raw?
{ key: variable.key, value: variable.value, raw: true }
else
{ key: variable.key, value: ::ExpandVariables.expand(variable.value, expand_variables) }
@ -306,7 +306,7 @@ module Ci
return [] unless pipeline.pipeline_schedule
pipeline.pipeline_schedule.variables.to_a.map do |variable|
if variable.raw? && ci_raw_variables_in_yaml_config_enabled?
if variable.raw?
{ key: variable.key, value: variable.value, raw: true }
else
{ key: variable.key, value: ::ExpandVariables.expand(variable.value, expand_variables) }
@ -329,12 +329,6 @@ module Ci
result.nil? ? FORWARD_DEFAULTS[:pipeline_variables] : result
end
end
def ci_raw_variables_in_yaml_config_enabled?
strong_memoize(:ci_raw_variables_in_yaml_config_enabled) do
::Feature.enabled?(:ci_raw_variables_in_yaml_config, project)
end
end
end
end

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
module Analytics
module CycleAnalytics
module Parentable
extend ActiveSupport::Concern
included do
belongs_to :namespace, class_name: 'Namespace', foreign_key: :group_id, optional: false # rubocop: disable Rails/InverseOf
validate :ensure_namespace_type
def ensure_namespace_type
return if namespace.nil?
return if namespace.is_a?(::Namespaces::ProjectNamespace) || namespace.is_a?(::Group)
errors.add(:namespace, s_('CycleAnalytics|the assigned object is not supported'))
end
end
end
end
end

View File

@ -119,21 +119,23 @@ module Analytics
end
def validate_labels
validate_label_within_group(:start_event_label_id, start_event_label_id) if start_event_label_id_changed?
validate_label_within_group(:end_event_label_id, end_event_label_id) if end_event_label_id_changed?
validate_label_within_namespace(:start_event_label_id, start_event_label_id) if start_event_label_id_changed?
validate_label_within_namespace(:end_event_label_id, end_event_label_id) if end_event_label_id_changed?
end
def validate_label_within_group(association_name, label_id)
def validate_label_within_namespace(association_name, label_id)
return unless label_id
return unless group
unless label_available_for_group?(label_id)
unless label_available_for_namespace?(label_id)
errors.add(association_name, s_('CycleAnalyticsStage|is not available for the selected group'))
end
end
def label_available_for_group?(label_id)
LabelsFinder.new(nil, { group_id: group.id, include_ancestor_groups: true, only_group_labels: true })
def label_available_for_namespace?(label_id)
subject = is_a?(::Analytics::CycleAnalytics::GroupStage) ? namespace : project.group
return unless subject
LabelsFinder.new(nil, { group_id: subject.id, include_ancestor_groups: true, only_group_labels: true })
.execute(skip_authorization: true)
.id_in(label_id)
.exists?

View File

@ -294,7 +294,7 @@ class Deployment < ApplicationRecord
end
def older_than_last_successful_deployment?
last_deployment_id = environment.last_deployment&.id
last_deployment_id = environment&.last_deployment&.id
return false unless last_deployment_id.present?
return false if self.id == last_deployment_id

View File

@ -75,6 +75,7 @@ class Integration < ApplicationRecord
attribute :active, default: false
attribute :alert_events, default: true
attribute :incident_events, default: false
attribute :category, default: 'common'
attribute :commit_events, default: true
attribute :confidential_issues_events, default: true

View File

@ -10,7 +10,7 @@ module Integrations
SUPPORTED_EVENTS = %w[
push issue confidential_issue merge_request note confidential_note
tag_push pipeline wiki_page deployment
tag_push pipeline wiki_page deployment incident
].freeze
SUPPORTED_EVENTS_FOR_LABEL_FILTER = %w[issue confidential_issue merge_request note confidential_note].freeze
@ -224,6 +224,7 @@ module Integrations
data.merge(project_url: project_url, project_name: project_name).with_indifferent_access
end
# rubocop:disable Metrics/CyclomaticComplexity
def get_message(object_kind, data)
case object_kind
when "push", "tag_push"
@ -240,8 +241,11 @@ module Integrations
Integrations::ChatMessage::WikiPageMessage.new(data)
when "deployment"
Integrations::ChatMessage::DeploymentMessage.new(data) if notify_for_ref?(data)
when "incident"
Integrations::ChatMessage::IssueMessage.new(data) unless update?(data)
end
end
# rubocop:enable Metrics/CyclomaticComplexity
def build_event_channels
event_channel_names.map do |channel_field|

View File

@ -9,6 +9,7 @@ module Integrations
attr_reader :action
attr_reader :state
attr_reader :description
attr_reader :object_kind
def initialize(params)
super
@ -21,6 +22,7 @@ module Integrations
@action = obj_attr[:action]
@state = obj_attr[:state]
@description = obj_attr[:description] || ''
@object_kind = params[:object_kind]
end
def attachments
@ -32,7 +34,7 @@ module Integrations
def activity
{
title: "Issue #{state} by #{strip_markup(user_combined_name)}",
title: "#{issue_type} #{state} by #{strip_markup(user_combined_name)}",
subtitle: "in #{project_link}",
text: issue_link,
image: user_avatar
@ -42,7 +44,7 @@ module Integrations
private
def message
"[#{project_link}] Issue #{issue_link} #{state} by #{strip_markup(user_combined_name)}"
"[#{project_link}] #{issue_type} #{issue_link} #{state} by #{strip_markup(user_combined_name)}"
end
def opened_issue?
@ -69,6 +71,10 @@ module Integrations
def issue_title
"#{Issue.reference_prefix}#{issue_iid} #{strip_markup(title)}"
end
def issue_type
@issue_type ||= object_kind == 'incident' ? 'Incident' : 'Issue'
end
end
end
end

View File

@ -270,6 +270,7 @@ class Project < ApplicationRecord
has_many :integrations
has_many :alert_hooks_integrations, -> { alert_hooks }, class_name: 'Integration'
has_many :incident_hooks_integrations, -> { incident_hooks }, class_name: 'Integration'
has_many :archive_trace_hooks_integrations, -> { archive_trace_hooks }, class_name: 'Integration'
has_many :confidential_issue_hooks_integrations, -> { confidential_issue_hooks }, class_name: 'Integration'
has_many :confidential_note_hooks_integrations, -> { confidential_note_hooks }, class_name: 'Integration'

View File

@ -33,12 +33,10 @@ module Ci
end
def runner_variables
stop_expanding_raw_refs = ::Feature.enabled?(:ci_raw_variables_in_yaml_config, project)
variables
.sort_and_expand_all(keep_undefined: true,
expand_file_refs: false,
expand_raw_refs: !stop_expanding_raw_refs)
expand_raw_refs: false)
.to_runner_variables
end

View File

@ -97,6 +97,16 @@ module Issues
hooks_scope = issue.confidential? ? :confidential_issue_hooks : :issue_hooks
issue.project.execute_hooks(issue_data, hooks_scope)
issue.project.execute_integrations(issue_data, hooks_scope)
execute_incident_hooks(issue, issue_data) if issue.incident?
end
# We can remove this code after proposal in
# https://gitlab.com/gitlab-org/gitlab/-/issues/367550#proposal is updated.
def execute_incident_hooks(issue, issue_data)
issue_data[:object_kind] = 'incident'
issue_data[:event_type] = 'incident'
issue.project.execute_integrations(issue_data, :incident_hooks)
end
def update_project_counter_caches?(issue)

View File

@ -91,10 +91,10 @@ class NotificationService
end
# Notify the user when at least one of their personal access tokens has expired today
def access_token_expired(user)
def access_token_expired(user, token_names = [])
return unless user.can?(:receive_notifications)
mailer.access_token_expired_email(user).deliver_later
mailer.access_token_expired_email(user, token_names).deliver_later
end
# Notify the user when one of their personal access tokens is revoked

View File

@ -25,13 +25,15 @@
.todo-body.gl-mb-2.gl-px-2.gl-display-flex.gl-align-items-flex-start.gl-lg-align-items-center
.todo-avatar.gl-display-none.gl-sm-display-inline-block
= author_avatar(todo, size: 24)
.todo-note{ :class => ("gl-display-flex gl-flex-direction-column" if todo.note.present? && two_line_mention) }
.todo-note
- if todo_author_display?(todo)
.author-name.bold.gl-display-inline
.author-name.bold.gl-display-inline<
- if todo.author
= link_to_author(todo, self_added: todo.self_added?)
- else
= _('(removed)')
- if todo.note.present? && two_line_mention
\:
%span.action-name{ data: { qa_selector: "todo_action_name_content" } }<
- if two_line_mention
@ -52,7 +54,8 @@
\.
- if todo.note.present?
%span.action-description{ :class => ("gl-font-style-italic" if !two_line_mention) }<
= first_line_in_markdown(todo, :body, 100, is_todo: true, project: todo.project, group: todo.group)
- max_chars = two_line_mention ? 125 : 100
= first_line_in_markdown(todo, :body, max_chars, is_todo: true, project: todo.project, group: todo.group)
.todo-timestamp.gl-white-space-nowrap.gl-sm-ml-3.gl-mt-2.gl-mb-2.gl-sm-my-0.gl-px-2.gl-sm-px-0
%span.todo-timestamp.gl-font-sm.gl-text-gray-500

View File

@ -1,7 +1,15 @@
%p
= _('Hi %{username}!') % { username: sanitize_name(@user.name) }
%p
= _('One or more of your personal access tokens has expired.')
- if @token_names.empty?
= _('One or more of your personal access tokens has expired.')
- else
= _('The following personal access tokens have expired:')
%p
%ul
- @token_names.each do |token|
%li= token
%p
- pat_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url }
- pat_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url }
= html_escape(_('You can create a new one or check them in your %{pat_link_start}personal access tokens%{pat_link_end} settings.')) % { pat_link_start: pat_link_start, pat_link_end: '</a>'.html_safe }

View File

@ -1,5 +1,13 @@
<%= _('Hi %{username}!') % { username: sanitize_name(@user.name) } %>
<%= _('One or more of your personal access tokens has expired.') %>
<%- if @token_names.empty? -%>
<%= _('One or more of your personal access tokens have expired.') %>
<%- else -%>
<%= _('The following personal access tokens have expired:') %>
<%- @token_names.each do |token| -%>
- <%= token %>
<%- end -%>
<%- end -%>
<%= _('You can create a new one or check them in your personal access tokens settings %{pat_link}.') % { pat_link: @target_url } %>

View File

@ -19,7 +19,7 @@ module Issues
return if project_id.nil? && root_namespace_id.nil?
return if ::Gitlab::Issues::Rebalancing::State.rebalance_recently_finished?(project_id, root_namespace_id)
# pull the projects collection to be rebalanced either the project if namespace is not a group(i.e. user namesapce)
# pull the projects collection to be rebalanced either the project if namespace is not a group(i.e. user namespace)
# or the root namespace, this also makes the worker backward compatible with previous version where a project_id was
# passed as the param
projects_to_rebalance = projects_collection(project_id, root_namespace_id)

View File

@ -10,16 +10,28 @@ module PersonalAccessTokens
feature_category :authentication_and_authorization
MAX_TOKENS = 100
def perform(*args)
notification_service = NotificationService.new
User.with_personal_access_tokens_expired_today.find_each do |user|
with_context(user: user) do
Gitlab::AppLogger.info "#{self.class}: Notifying User #{user.id} about an expired token"
expiring_user_tokens = user.personal_access_tokens.without_impersonation.expired_today_and_not_notified
notification_service.access_token_expired(user)
# rubocop: disable CodeReuse/ActiveRecord
# We never materialise the token instances. We need the names to mention them in the
# email. Later we trigger an update query on the entire relation, not on individual instances.
token_names = expiring_user_tokens.limit(MAX_TOKENS).pluck(:name)
# rubocop: enable CodeReuse/ActiveRecord
user.personal_access_tokens.without_impersonation.expired_today_and_not_notified.update_all(after_expiry_notification_delivered: true)
notification_service.access_token_expired(user, token_names)
Gitlab::AppLogger.info "#{self.class}: Notifying User #{user.id} about expired tokens"
expiring_user_tokens.each_batch do |expiring_tokens|
expiring_tokens.update_all(after_expiry_notification_delivered: true)
end
end
end
end

View File

@ -20,7 +20,6 @@ module Projects
delete_service_result = ::Branches::DeleteService.new(project, user)
.execute(branch_name)
return unless Feature.enabled?(:track_and_raise_delete_source_errors, project)
# Only want to raise on 400 to avoid permission and non existant branch error
return unless delete_service_result[:http_status] == 400

View File

@ -1,8 +0,0 @@
---
name: ci_raw_variables_in_yaml_config
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/98420
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375034
milestone: '15.6'
type: development
group: group::pipeline authoring
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: track_and_raise_delete_source_errors
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103842
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/382440
milestone: '15.6'
type: development
group: group::code review
default_enabled: false

View File

@ -312,6 +312,11 @@ module.exports = {
test: /\.mjs$/,
use: [],
},
{
test: /(@cubejs-client\/vue).*\.(js)?$/,
include: /node_modules/,
loader: 'babel-loader',
},
WEBPACK_USE_ESBUILD_LOADER && {
test: /\.(js|cjs)$/,
exclude: (modulePath) =>

View File

@ -4384,10 +4384,7 @@ variables:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/353991) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_raw_variables_in_yaml_config`. Disabled by default.
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/375034) in GitLab 15.6.
> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/375034) in GitLab 15.7.
FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature per project,
ask an administrator to [disable the feature flag](../../administration/feature_flags.md) named `ci_raw_variables_in_yaml_config`.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/375034) in GitLab 15.8. Feature flag `ci_raw_variables_in_yaml_config` removed.
Use the `expand` keyword to configure a variable to be expandable or not.

View File

@ -40,7 +40,7 @@ export default {
summary(data) {}, // Required: Level 1 summary text
statusIcon(data) {}, // Required: Level 1 status icon
tertiaryButtons() {}, // Optional: Level 1 action buttons
shouldCollapse() {}, // Optional: Add logic to determine if the widget can expand or not
shouldCollapse(data) {}, // Optional: Add logic to determine if the widget can expand or not
},
methods: {
fetchCollapsedData(props) {}, // Required: Fetches data required for collapsed state

View File

@ -61,6 +61,41 @@ Inspiration:
- <https://tailwindcss.com/docs/utility-first>
- <https://tailwindcss.com/docs/extracting-components>
#### Utility mixins
In addition to utility classes GitLab UI provides utility mixins named after the utility classes.
For example a utility class `.gl-mt-3` will have a corresponding mixin `gl-mt-3`. Here's how it can be used in an SCSS file:
```scss
.my-class {
@include gl-mt-3;
}
```
These mixins should be used to replace _magic values_ in our code.
For example a `margin-top: 8px` is a good candidate for the `@include gl-mt-3` mixin replacement.
Avoid using utility mixins for [pre-defined CSS keywords](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Values_and_Units#pre-defined_keyword_values).
For example prefer `display: flex` over `@include gl-display-flex`.
```scss
// Bad
.my-class {
@include gl-display-flex;
}
// Good
.my-class {
display: flex;
}
// Good
.my-class {
@include gl-mt-3;
}
```
### Naming
Filenames should use `snake_case`.

View File

@ -1362,17 +1362,6 @@ Payload example:
## Job events
- Number of retries (`retries_count`) [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/382046) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md)
named `job_webhook_retries_count`. Disabled by default.
- Pipeline name (`commit.name`) [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107963) in GitLab 15.8 [with a flag](../../../administration/feature_flags.md)
named `pipeline_name`. Enabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available,
ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named
`job_webhook_retries_count`.
On GitLab.com, this feature is not available.
Job events are triggered when the status of a job changes.
The `commit.id` in the payload is the ID of the pipeline, not the ID of the commit.
@ -1405,7 +1394,7 @@ Payload example:
"build_duration": null,
"build_allow_failure": false,
"build_failure_reason": "script_failure",
"retries_count": 2, // 2 indicates this is the 2nd retry of this job
"retries_count": 2, // the second retry of this job
"pipeline_id": 2366,
"project_id": 380,
"project_name": "gitlab-org/gitlab-test",
@ -1450,6 +1439,32 @@ Payload example:
}
```
### Number of retries
> `retries_count` [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/382046) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `job_webhook_retries_count`. Disabled by default.
FLAG:
On self-managed GitLab, by default this feature is not available. To make it available,
ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named
`job_webhook_retries_count`.
On GitLab.com, this feature is not available.
`retries_count` is an integer that indicates if the job is a retry. `0` means that the job
has not been retried. `1` means that it's the first retry.
### Pipeline name
> `commit.name` [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107963) in GitLab 15.8 [with a flag](../../../administration/feature_flags.md) named `pipeline_name`. Enabled by default.
FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature,
ask an administrator to [disable the feature flag](../../../administration/feature_flags.md) named
`pipeline_name`.
On GitLab.com, this feature is available.
You can set custom names for pipelines with [`workflow:name`](../../../ci/yaml/index.md#workflowname).
If the pipeline has a name, that name is the value of `commit.name`.
## Deployment events
Deployment events are triggered when a deployment:

View File

@ -24,6 +24,7 @@ module API
helpers do
params :filter_params do
optional :search, type: String, desc: 'Return list of branches matching the search criteria'
optional :regex, type: String, desc: 'Return list of branches matching the regex'
optional :sort, type: String, desc: 'Return list of branches sorted by the given field', values: %w[name_asc updated_asc updated_desc]
end
end

View File

@ -14,6 +14,7 @@ module API
expose :commit_events, documentation: { type: 'boolean' }
expose :push_events, documentation: { type: 'boolean' }
expose :issues_events, documentation: { type: 'boolean' }
expose :incident_events, documentation: { type: 'boolean' }
expose :confidential_issues_events, documentation: { type: 'boolean' }
expose :merge_requests_events, documentation: { type: 'boolean' }
expose :tag_push_events, documentation: { type: 'boolean' }

View File

@ -61,6 +61,12 @@ module API
type: String,
desc: 'The name of the channel to receive issues_events notifications'
},
{
required: false,
name: :incident_channel,
type: String,
desc: 'The name of the channel to receive incident_events notifications'
},
{
required: false,
name: :confidential_issue_channel,
@ -114,6 +120,12 @@ module API
type: Boolean,
desc: 'Enable notifications for issues_events'
},
{
required: false,
name: :incident_events,
type: Boolean,
desc: 'Enable notifications for incident_events'
},
{
required: false,
name: :confidential_issues_events,

View File

@ -118,14 +118,14 @@ module Gitlab
end
def aggregation
@aggregation ||= ::Analytics::CycleAnalytics::Aggregation.safe_create_for_group(group)
@aggregation ||= ::Analytics::CycleAnalytics::Aggregation.safe_create_for_namespace(group)
end
def group_data_attributes
{
id: group.id,
namespace_id: group.id,
name: group.name,
parent_id: group.parent_id,
full_path: group.full_path,
avatar_url: group.avatar_url
}

View File

@ -54,9 +54,7 @@ module Gitlab
validates :key, alphanumeric: true
validates :config_value, alphanumeric: true, allow_nil: true
validates :config_description, alphanumeric: true, allow_nil: true
validates :config_expand, boolean: true, allow_nil: true, if: -> {
ci_raw_variables_in_yaml_config_enabled?
}
validates :config_expand, boolean: true, allow_nil: true
validates :config_options, array_of_strings: true, allow_nil: true
validate do
@ -82,16 +80,10 @@ module Gitlab
end
def value_with_data
if ci_raw_variables_in_yaml_config_enabled?
{
value: config_value.to_s,
raw: (!config_expand if has_config_expand?)
}.compact
else
{
value: config_value.to_s
}.compact
end
{
value: config_value.to_s,
raw: (!config_expand if has_config_expand?)
}.compact
end
def value_with_prefill_data
@ -100,10 +92,6 @@ module Gitlab
options: config_options
).compact
end
def ci_raw_variables_in_yaml_config_enabled?
YamlProcessor::FeatureFlags.enabled?(:ci_raw_variables_in_yaml_config)
end
end
class UnknownStrategy < ::Gitlab::Config::Entry::Node

View File

@ -72,7 +72,6 @@ module Gitlab
Collection.new(@variables.reject(&block))
end
# `expand_raw_refs` will be deleted with the FF `ci_raw_variables_in_yaml_config`.
def expand_value(value, keep_undefined: false, expand_file_refs: true, expand_raw_refs: true)
value.gsub(Item::VARIABLES_REGEXP) do
match = Regexp.last_match # it is either a valid variable definition or a ($$ / %%)
@ -90,8 +89,18 @@ module Gitlab
if variable.file?
expand_file_refs ? variable.value : full_match
elsif variable.raw?
# With `full_match`, we defer the expansion of raw variables to the runner. If we expand them here,
# the runner will not know the expanded value is a raw variable and it tries to expand it again.
# Normally, it's okay to expand a raw variable if it's referenced in another variable because
# its rawness is not broken. However, the runner also tries to expand variables.
# Here, with `full_match`, we defer the expansion of raw variables to the runner.
# If we expand them here, the runner will not know that the expanded value is a raw variable
# and it tries to expand it again.
# Example: `A` is a normal variable with value `normal`.
# `B` is a raw variable with value `raw-$A`.
# `C` is a normal variable with value `$B`.
# If we expanded `C` here, the runner would receive `C` as `raw-$A`. And since `A` is a normal
# variable, the runner would expand it. So, the result would be `raw-normal`.
# With `full_match`, the runner receives `C` as `$B`. And since `B` is a raw variable, the
# runner expanded it as `raw-$A`, which is what we want.
# Discussion: https://gitlab.com/gitlab-org/gitlab/-/issues/353991#note_1103274951
expand_raw_refs ? variable.value : full_match
else
@ -106,7 +115,6 @@ module Gitlab
end
end
# `expand_raw_refs` will be deleted with the FF `ci_raw_variables_in_yaml_config`.
def sort_and_expand_all(keep_undefined: false, expand_file_refs: true, expand_raw_refs: true)
sorted = Sort.new(self)
return self.class.new(self, sorted.errors) unless sorted.valid?

View File

@ -64,12 +64,7 @@ module Gitlab
private
def assign_valid_attributes
@root_variables = if YamlProcessor::FeatureFlags.enabled?(:ci_raw_variables_in_yaml_config)
transform_to_array(@ci_config.variables_with_data)
else
transform_to_array(@ci_config.variables)
end
@root_variables = transform_to_array(@ci_config.variables_with_data)
@root_variables_with_prefill_data = @ci_config.variables_with_prefill_data
@stages = @ci_config.stages

View File

@ -11367,6 +11367,9 @@ msgstr ""
msgid "Country"
msgstr ""
msgid "Counts"
msgstr ""
msgid "Counts reflect children you may not have access to."
msgstr ""
@ -12263,6 +12266,9 @@ msgstr ""
msgid "CycleAnalytics|project dropdown filter"
msgstr ""
msgid "CycleAnalytics|the assigned object is not supported"
msgstr ""
msgid "DAG visualization requires at least 3 dependent jobs."
msgstr ""
@ -28984,6 +28990,9 @@ msgstr ""
msgid "One or more of your personal access tokens has expired."
msgstr ""
msgid "One or more of your personal access tokens have expired."
msgstr ""
msgid "One or more of your personal access tokens will expire in %{days_to_expire} days or less:"
msgstr ""
@ -31725,30 +31734,177 @@ msgstr ""
msgid "Product analytics"
msgstr ""
msgid "ProductAnalytics|Add another dimension"
msgstr ""
msgid "ProductAnalytics|Add to Dashboard"
msgstr ""
msgid "ProductAnalytics|All clicks compared"
msgstr ""
msgid "ProductAnalytics|All events compared"
msgstr ""
msgid "ProductAnalytics|All features"
msgstr ""
msgid "ProductAnalytics|All pages"
msgstr ""
msgid "ProductAnalytics|An error occured while loading the %{widgetTitle} widget."
msgstr ""
msgid "ProductAnalytics|An error occurred while fetching data. Refresh the page to try again."
msgstr ""
msgid "ProductAnalytics|Any Click on elements"
msgstr ""
msgid "ProductAnalytics|Audience"
msgstr ""
msgid "ProductAnalytics|Browser"
msgstr ""
msgid "ProductAnalytics|Browser Family"
msgstr ""
msgid "ProductAnalytics|Choose a chart type on the right"
msgstr ""
msgid "ProductAnalytics|Choose a measurement to start"
msgstr ""
msgid "ProductAnalytics|Click Events"
msgstr ""
msgid "ProductAnalytics|Code"
msgstr ""
msgid "ProductAnalytics|Compares all events against each other"
msgstr ""
msgid "ProductAnalytics|Compares click events against each other"
msgstr ""
msgid "ProductAnalytics|Compares feature usage of all features against each other"
msgstr ""
msgid "ProductAnalytics|Compares pageviews of all pages against each other"
msgstr ""
msgid "ProductAnalytics|Dashboards are created by editing the projects dashboard files."
msgstr ""
msgid "ProductAnalytics|Data"
msgstr ""
msgid "ProductAnalytics|Data Table"
msgstr ""
msgid "ProductAnalytics|Dimensions"
msgstr ""
msgid "ProductAnalytics|Event Type"
msgstr ""
msgid "ProductAnalytics|Events"
msgstr ""
msgid "ProductAnalytics|Events grouped by %{granularity}"
msgstr ""
msgid "ProductAnalytics|Events over time"
msgstr ""
msgid "ProductAnalytics|Feature Usage"
msgstr ""
msgid "ProductAnalytics|Feature usage"
msgstr ""
msgid "ProductAnalytics|Host"
msgstr ""
msgid "ProductAnalytics|Language"
msgstr ""
msgid "ProductAnalytics|Line Chart"
msgstr ""
msgid "ProductAnalytics|Measure All tracked Events"
msgstr ""
msgid "ProductAnalytics|Measure all or specific Page Views"
msgstr ""
msgid "ProductAnalytics|Measuring"
msgstr ""
msgid "ProductAnalytics|New Analytics Widget Title"
msgstr ""
msgid "ProductAnalytics|OS"
msgstr ""
msgid "ProductAnalytics|OS Version"
msgstr ""
msgid "ProductAnalytics|On what do you want to get insights?"
msgstr ""
msgid "ProductAnalytics|Page Language"
msgstr ""
msgid "ProductAnalytics|Page Path"
msgstr ""
msgid "ProductAnalytics|Page Title"
msgstr ""
msgid "ProductAnalytics|Page Views"
msgstr ""
msgid "ProductAnalytics|Pages"
msgstr ""
msgid "ProductAnalytics|Product analytics dashboards"
msgstr ""
msgid "ProductAnalytics|Referer"
msgstr ""
msgid "ProductAnalytics|Resulting Data"
msgstr ""
msgid "ProductAnalytics|There is no data for this type of chart currently. Please see the Setup tab if you have not configured the product analytics tool already."
msgstr ""
msgid "ProductAnalytics|Track specific features"
msgstr ""
msgid "ProductAnalytics|URL"
msgstr ""
msgid "ProductAnalytics|User activity"
msgstr ""
msgid "ProductAnalytics|Users"
msgstr ""
msgid "ProductAnalytics|Viewport"
msgstr ""
msgid "ProductAnalytics|Visualization Type"
msgstr ""
msgid "ProductAnalytics|What do you want to measure?"
msgstr ""
msgid "ProductAnalytics|Widget"
msgstr ""
msgid "Productivity"
msgstr ""
@ -32634,6 +32790,9 @@ msgstr ""
msgid "ProjectService|Trigger event when a wiki page is created or updated."
msgstr ""
msgid "ProjectService|Trigger event when an incident is created."
msgstr ""
msgid "ProjectService|Trigger event when an issue is created, updated, or closed."
msgstr ""
@ -41662,6 +41821,9 @@ msgid_plural "The following personal access tokens: %{token_names} were revoked,
msgstr[0] ""
msgstr[1] ""
msgid "The following personal access tokens have expired:"
msgstr ""
msgid "The fork relationship has been removed."
msgstr ""
@ -48444,7 +48606,7 @@ msgstr ""
msgid "Your password reset token has expired."
msgstr ""
msgid "Your personal access token has expired"
msgid "Your personal access tokens have expired"
msgstr ""
msgid "Your personal access tokens will expire in %{days_to_expire} days or less"

View File

@ -52,7 +52,8 @@
"@babel/core": "^7.18.5",
"@babel/preset-env": "^7.18.2",
"@codesandbox/sandpack-client": "^1.2.2",
"@cubejs-client/core": "^0.31.0",
"@cubejs-client/core": "^0.31.15",
"@cubejs-client/vue": "^0.31.19",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.0.1",

View File

@ -24,36 +24,29 @@ module QA
private
# Check if a group exists in private or public tab
# @param name [String] group name
# @return [Boolean] whether a group with given name exists
def has_filtered_group?(name)
# Filter and submit to reload the page and only retrieve the filtered results
find_element(:groups_filter_field).set(name).send_keys(:return)
filter_group(name)
return true if page.has_link?(name, wait: 0) # element containing link to group
# Since we submitted after filtering, the presence of
# groups_list_tree_container means we have the complete filtered list
# of groups
has_element?(:groups_list_tree_container, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME)
# If there are no groups we'll know immediately because we filtered the list
if page.has_text?('No groups or projects matched your search',
wait: 0) || page.has_text?('No groups matched your search', wait: 0)
return false unless has_element?(:public_groups_tab)
return false unless has_element?(:public_groups_tab, wait: 0)
# Try for public groups
click_element(:public_groups_tab)
# Filter and submit to reload the page and only retrieve the filtered results
find_element(:groups_filter_field).set(name).send_keys(:return)
# Since we submitted after filtering, the presence of
# groups_list_tree_container means we have the complete filtered list
# of groups
has_element?(:groups_list_tree_container, wait: QA::Support::Repeater::DEFAULT_MAX_WAIT_TIME)
return false if page.has_text?('No groups or projects matched your search',
wait: 0) || page.has_text?('No groups matched your search', wait: 0)
end
# The name will be present as filter input so we check for a link, not text
# Check public groups
click_element(:public_groups_tab)
filter_group(name)
page.has_link?(name, wait: 0)
end
# Filter by group name
# @param name [String] group name
# @return [Boolean] whether the filter returned any group
def filter_group(name)
fill_element(:groups_filter_field, name).send_keys(:return)
finished_loading?
has_element?(:groups_list_tree_container, wait: 1)
end
end
end
end

View File

@ -2,10 +2,7 @@
module QA
RSpec.describe 'Verify', :runner do
describe 'Pipeline with raw variables in YAML', product_group: :pipeline_authoring, feature_flag: {
name: 'ci_raw_variables_in_yaml_config',
scope: :project
} do
describe 'Pipeline with raw variables in YAML', product_group: :pipeline_authoring do
let(:executor) { "qa-runner-#{Time.now.to_i}" }
let(:pipeline_job_name) { 'rspec' }
@ -23,7 +20,7 @@ module QA
end
end
let(:commit_ci_file) do
let!(:commit_ci_file) do
Resource::Repository::Commit.fabricate_via_api! do |commit|
commit.project = project
commit.commit_message = 'Add .gitlab-ci.yml'
@ -76,10 +73,7 @@ module QA
let(:pipeline_id) { project.pipelines.first[:id] }
let(:job_id) { project.job_by_name(pipeline_job_name)[:id] }
def before_do
# TODO: Switch to use `let!` and remove this line when removing FF
commit_ci_file
before do
Flow::Login.sign_in
project.visit!
Flow::Pipeline.visit_latest_pipeline(status: 'passed')
@ -92,55 +86,20 @@ module QA
runner&.remove_via_api!
end
context 'when FF is on', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/381487' do
before do
Runtime::Feature.enable(:ci_raw_variables_in_yaml_config, project: project)
sleep 60
before_do
end
it 'expands variables according to expand: true/false', :aggregate_failures do
Page::Project::Job::Show.perform do |show|
expect(show.output).to have_content("VAR1 is JOBID-#{job_id}")
expect(show.output).to have_content("VAR2 is PIPELINEID-#{pipeline_id} and JOBID-#{job_id}")
expect(show.output).to have_content("VAR3 is PIPELINEID-$CI_PIPELINE_ID and $VAR1")
expect(show.output).to have_content("VAR4 is JOBID-$CI_JOB_ID")
expect(show.output).to have_content("VAR5 is PIPELINEID-#{pipeline_id} and JOBID-$CI_JOB_ID")
expect(show.output).to have_content("VAR6 is PIPELINEID-$CI_PIPELINE_ID and $VAR4")
expect(show.output).to have_content("VAR7 is overridden value 7 #{pipeline_id}")
expect(show.output).to have_content("VAR8 is value 8 $CI_PIPELINE_ID")
end
end
end
# TODO: Remove this context when FF :ci_raw_variables_in_yaml_config is removed
# Also archive testcase and close related issue
context 'when FF is off',
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/381486',
quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/381806',
only: { pipeline: %w[staging staging-canary staging-ref] },
type: :waiting_on
} do
before do
Runtime::Feature.disable(:ci_raw_variables_in_yaml_config, project: project)
sleep 60
before_do
end
it 'expands all variables', :aggregate_failures do
Page::Project::Job::Show.perform do |show|
expect(show.output).to have_content("VAR1 is JOBID-#{job_id}")
expect(show.output).to have_content("VAR2 is PIPELINEID-#{pipeline_id} and JOBID-#{job_id}")
expect(show.output).to have_content("VAR3 is PIPELINEID-#{pipeline_id} and JOBID-#{job_id}")
expect(show.output).to have_content("VAR4 is JOBID-#{job_id}")
expect(show.output).to have_content("VAR5 is PIPELINEID-#{pipeline_id} and JOBID-#{job_id}")
expect(show.output).to have_content("VAR6 is PIPELINEID-#{pipeline_id} and JOBID-#{job_id}")
expect(show.output).to have_content("VAR7 is overridden value 7 #{pipeline_id}")
expect(show.output).to have_content("VAR8 is value 8 #{pipeline_id}")
end
it(
'expands variables according to expand: true/false',
:aggregate_failures,
testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/381487'
) do
Page::Project::Job::Show.perform do |show|
expect(show.output).to have_content("VAR1 is JOBID-#{job_id}")
expect(show.output).to have_content("VAR2 is PIPELINEID-#{pipeline_id} and JOBID-#{job_id}")
expect(show.output).to have_content("VAR3 is PIPELINEID-$CI_PIPELINE_ID and $VAR1")
expect(show.output).to have_content("VAR4 is JOBID-$CI_JOB_ID")
expect(show.output).to have_content("VAR5 is PIPELINEID-#{pipeline_id} and JOBID-$CI_JOB_ID")
expect(show.output).to have_content("VAR6 is PIPELINEID-$CI_PIPELINE_ID and $VAR4")
expect(show.output).to have_content("VAR7 is overridden value 7 #{pipeline_id}")
expect(show.output).to have_content("VAR8 is value 8 $CI_PIPELINE_ID")
end
end
end

View File

@ -2,7 +2,7 @@
FactoryBot.define do
factory :cycle_analytics_aggregation, class: 'Analytics::CycleAnalytics::Aggregation' do
group
namespace { association(:group) }
enabled { true }

View File

@ -18,7 +18,7 @@ RSpec.describe 'User uploads new design', :js, feature_category: :design_managem
context "when the feature is available" do
let(:feature_enabled) { true }
it 'uploads designs' do
it 'uploads designs', quarantine: 'https://gitlab.com/gitlab-org/gitlab/-/issues/358845' do
upload_design(logo_fixture, count: 1)
expect(page).to have_selector('.js-design-list-item', count: 1)

View File

@ -2,7 +2,7 @@
require 'spec_helper'
RSpec.describe BranchesFinder do
RSpec.describe BranchesFinder, feature_category: :source_code_management do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:repository) { project.repository }
@ -127,6 +127,34 @@ RSpec.describe BranchesFinder do
end
end
context 'by invalid regex' do
let(:params) { { regex: '[' } }
it { expect { subject }.to raise_error(RegexpError) }
end
context 'by `|` regex' do
let(:params) { { regex: 'audio|add-ipython-files' } }
it 'filters branches' do
branches = subject
expect(branches.first.name).to eq('add-ipython-files')
expect(branches.second.name).to eq('audio')
expect(branches.count).to eq(2)
end
end
context 'by exclude name' do
let(:params) { { regex: '^[^a]' } }
it 'filters branches' do
result = subject
result.each do |branch|
expect(branch.name).not_to start_with('a')
end
end
end
context 'by name with multiple wildcards' do
let(:params) { { search: 'f*a*e' } }

View File

@ -30,6 +30,9 @@
"issues_events": {
"type": "boolean"
},
"incident_events": {
"type": "boolean"
},
"confidential_issues_events": {
"type": "boolean"
},

View File

@ -0,0 +1,26 @@
let mockLoad = jest.fn();
let mockMetadata = jest.fn();
export const CubejsApi = jest.fn().mockImplementation(() => ({
load: mockLoad,
meta: mockMetadata,
}));
export const HttpTransport = jest.fn();
export const GRANULARITIES = [
{
name: 'seconds',
title: 'Seconds',
},
];
// eslint-disable-next-line no-underscore-dangle
export const __setMockLoad = (x) => {
mockLoad = x;
};
// eslint-disable-next-line no-underscore-dangle
export const __setMockMetadata = (x) => {
mockMetadata = x;
};

View File

@ -102,6 +102,7 @@ describe('WorkItemLinksForm', () => {
preventDefault: jest.fn(),
});
await waitForPromises();
expect(wrapper.vm.childWorkItemType).toEqual('gid://gitlab/WorkItems::Type/3');
expect(createMutationResolver).toHaveBeenCalledWith({
input: {
title: 'Create task test',
@ -124,6 +125,7 @@ describe('WorkItemLinksForm', () => {
preventDefault: jest.fn(),
});
await waitForPromises();
expect(wrapper.vm.childWorkItemType).toEqual('gid://gitlab/WorkItems::Type/3');
expect(createMutationResolver).toHaveBeenCalledWith({
input: {
title: 'Create confidential task',

View File

@ -257,14 +257,6 @@ RSpec.describe Gitlab::Ci::Config::Entry::Variable do
subject(:value_with_data) { entry.value_with_data }
it { is_expected.to eq(value: 'value', raw: true) }
context 'when the FF ci_raw_variables_in_yaml_config is disabled' do
before do
stub_feature_flags(ci_raw_variables_in_yaml_config: false)
end
it { is_expected.to eq(value: 'value') }
end
end
context 'when config expand is true' do

View File

@ -1302,32 +1302,6 @@ module Gitlab
'VAR3' => { value: 'value3', raw: true }
)
end
context 'when the FF ci_raw_variables_in_yaml_config is disabled' do
before do
stub_feature_flags(ci_raw_variables_in_yaml_config: false)
end
it 'returns variables without description and raw' do
expect(job_variables).to contain_exactly(
{ key: 'VAR4', value: 'value4' },
{ key: 'VAR5', value: 'value5' },
{ key: 'VAR6', value: 'value6' }
)
expect(execute.root_variables).to contain_exactly(
{ key: 'VAR1', value: 'value1' },
{ key: 'VAR2', value: 'value2' },
{ key: 'VAR3', value: 'value3' }
)
expect(execute.root_variables_with_prefill_data).to eq(
'VAR1' => { value: 'value1' },
'VAR2' => { value: 'value2', description: 'description2' },
'VAR3' => { value: 'value3' }
)
end
end
end
end

View File

@ -198,9 +198,10 @@ RSpec.describe Emails::Profile do
describe 'user personal access token has expired' do
let_it_be(:user) { create(:user) }
let_it_be(:pat) { create(:personal_access_token, user: user) }
context 'when valid' do
subject { Notify.access_token_expired_email(user) }
subject { Notify.access_token_expired_email(user, [pat.name]) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'it should not have Gmail Actions links'
@ -211,11 +212,12 @@ RSpec.describe Emails::Profile do
end
it 'has the correct subject' do
is_expected.to have_subject /Your personal access token has expired/
is_expected.to have_subject /Your personal access tokens have expired/
end
it 'mentions the access token has expired' do
is_expected.to have_body_text /One or more of your personal access tokens has expired/
is_expected.to have_body_text /The following personal access tokens have expired:/
is_expected.to have_body_text /#{pat.name}/
end
it 'includes a link to personal access tokens page' do

View File

@ -1,13 +1,13 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model do
RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model, feature_category: :value_stream_management do
describe 'associations' do
it { is_expected.to belong_to(:group).required }
it { is_expected.to belong_to(:namespace).required }
end
describe 'validations' do
it { is_expected.not_to validate_presence_of(:group) }
it { is_expected.not_to validate_presence_of(:namespace) }
it { is_expected.not_to validate_presence_of(:enabled) }
%i[incremental_runtimes_in_seconds incremental_processed_records full_runtimes_in_seconds full_processed_records].each do |column|
@ -18,6 +18,10 @@ RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model do
expect(record.errors).to have_key(column)
end
end
it_behaves_like 'value stream analytics namespace models' do
let(:factory_name) { :cycle_analytics_aggregation }
end
end
describe 'attribute updater methods' do
@ -126,19 +130,19 @@ RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model do
end
end
describe '#safe_create_for_group' do
describe '#safe_create_for_namespace' do
let_it_be(:group) { create(:group) }
let_it_be(:subgroup) { create(:group, parent: group) }
it 'creates the aggregation record' do
record = described_class.safe_create_for_group(group)
record = described_class.safe_create_for_namespace(group)
expect(record).to be_persisted
end
context 'when non top-level group is given' do
it 'creates the aggregation record for the top-level group' do
record = described_class.safe_create_for_group(subgroup)
record = described_class.safe_create_for_namespace(subgroup)
expect(record).to be_persisted
end
@ -146,11 +150,11 @@ RSpec.describe Analytics::CycleAnalytics::Aggregation, type: :model do
context 'when the record is already present' do
it 'does nothing' do
described_class.safe_create_for_group(group)
described_class.safe_create_for_namespace(group)
expect do
described_class.safe_create_for_group(group)
described_class.safe_create_for_group(subgroup)
described_class.safe_create_for_namespace(group)
described_class.safe_create_for_namespace(subgroup)
end.not_to change { described_class.count }
end
end

View File

@ -4,7 +4,7 @@ require 'spec_helper'
RSpec.describe Analytics::CycleAnalytics::ProjectStage do
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:project).required }
end
it 'default stages must be valid' do

View File

@ -375,25 +375,6 @@ RSpec.describe Ci::Bridge, feature_category: :continuous_integration do
{ key: 'VAR7', value: 'value7 $VAR1', raw: true }
)
end
context 'when the FF ci_raw_variables_in_yaml_config is disabled' do
before do
stub_feature_flags(ci_raw_variables_in_yaml_config: false)
end
it 'ignores the raw attribute' do
expect(downstream_variables).to contain_exactly(
{ key: 'BRIDGE', value: 'cross' },
{ key: 'VAR1', value: 'value1' },
{ key: 'VAR2', value: 'value2 value1' },
{ key: 'VAR3', value: 'value3 value1' },
{ key: 'VAR4', value: 'value4 value1' },
{ key: 'VAR5', value: 'value5 value1' },
{ key: 'VAR6', value: 'value6 value1' },
{ key: 'VAR7', value: 'value7 value1' }
)
end
end
end
end

View File

@ -96,7 +96,7 @@ RSpec.describe Ci::NamespaceMirror do
describe '.by_namespace_id' do
subject(:result) { described_class.by_namespace_id(group2.id) }
it 'returns namesapce mirrors of namespace id' do
it 'returns namespace mirrors of namespace id' do
expect(result).to contain_exactly(group2.ci_namespace_mirror)
end
end

View File

@ -402,6 +402,16 @@ RSpec.describe Deployment do
it { is_expected.to be_falsey }
end
context 'when environment is undefined' do
let(:deployment) { build(:deployment, :success, project: project, environment: environment) }
before do
deployment.environment = nil
end
it { is_expected.to be_falsey }
end
end
describe '#success?' do

View File

@ -29,6 +29,7 @@ RSpec.describe Integration do
it { is_expected.to be_tag_push_events }
it { is_expected.to be_wiki_page_events }
it { is_expected.not_to be_active }
it { is_expected.not_to be_incident_events }
it { expect(subject.category).to eq(:common) }
end

View File

@ -134,6 +134,12 @@ RSpec.describe Integrations::BaseChatNotification do
it_behaves_like 'notifies the chat integration'
end
context 'Incident events' do
let(:data) { issue.to_hook_data(user).merge!({ object_kind: 'incident' }) }
it_behaves_like 'notifies the chat integration'
end
end
context 'when labels_to_be_notified_behavior is not defined' do

View File

@ -7,6 +7,7 @@ RSpec.describe Integrations::ChatMessage::IssueMessage do
let(:args) do
{
object_kind: 'issue',
user: {
name: 'Test User',
username: 'test.user',

View File

@ -166,6 +166,7 @@ RSpec.describe Project, factory_default: :keep do
it { is_expected.to have_many(:wiki_page_hooks_integrations).class_name('Integration') }
it { is_expected.to have_many(:deployment_hooks_integrations).class_name('Integration') }
it { is_expected.to have_many(:alert_hooks_integrations).class_name('Integration') }
it { is_expected.to have_many(:incident_hooks_integrations).class_name('Integration') }
# GitLab Pages
it { is_expected.to have_many(:pages_domains) }

View File

@ -375,23 +375,6 @@ RSpec.describe Ci::BuildRunnerPresenter do
public: false, masked: false }
)
end
context 'when the FF ci_raw_variables_in_yaml_config is disabled' do
before do
stub_feature_flags(ci_raw_variables_in_yaml_config: false)
end
it 'returns expanded variables' do
expect(runner_variables).to include(
{ key: 'regular_var', value: 'value 1',
public: false, masked: false },
{ key: 'raw_var', value: 'value 2',
public: false, masked: false, raw: true },
{ key: 'var_with_variables', value: 'value 3 and value 1 and value 2 and $undefined_var',
public: false, masked: false }
)
end
end
end
end

View File

@ -60,27 +60,6 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes
{ key: 'VAR8', value: "value 8 $CI_PIPELINE_ID", public: true, masked: false, raw: true }
)
end
context 'when the FF ci_raw_variables_in_yaml_config is disabled' do
before do
stub_feature_flags(ci_raw_variables_in_yaml_config: false)
end
it 'creates the pipeline with a job that has all variables expanded' do
expect(pipeline).to be_created_successfully
expect(Ci::BuildRunnerPresenter.new(rspec).runner_variables).to include(
{ key: 'VAR1', value: "JOBID-#{rspec.id}", public: true, masked: false },
{ key: 'VAR2', value: "PIPELINEID-#{pipeline.id} and JOBID-#{rspec.id}", public: true, masked: false },
{ key: 'VAR3', value: "PIPELINEID-#{pipeline.id} and JOBID-#{rspec.id}", public: true, masked: false },
{ key: 'VAR4', value: "JOBID-#{rspec.id}", public: true, masked: false },
{ key: 'VAR5', value: "PIPELINEID-#{pipeline.id} and JOBID-#{rspec.id}", public: true, masked: false },
{ key: 'VAR6', value: "PIPELINEID-#{pipeline.id} and JOBID-#{rspec.id}", public: true, masked: false },
{ key: 'VAR7', value: "overridden value 7 #{pipeline.id}", public: true, masked: false },
{ key: 'VAR8', value: "value 8 #{pipeline.id}", public: true, masked: false }
)
end
end
end
context 'when trigger variables have expand: true/false' do
@ -109,22 +88,6 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes
{ key: 'VAR3', value: "PIPELINEID-$CI_PIPELINE_ID and $VAR1", raw: true }
)
end
context 'when the FF ci_raw_variables_in_yaml_config is disabled' do
before do
stub_feature_flags(ci_raw_variables_in_yaml_config: false)
end
it 'creates the pipeline with a job that has all variables expanded' do
expect(pipeline).to be_created_successfully
expect(child.downstream_variables).to include(
{ key: 'VAR1', value: "PROJECTID-#{project.id}" },
{ key: 'VAR2', value: "PIPELINEID-#{pipeline.id} and PROJECTID-$CI_PROJECT_ID" },
{ key: 'VAR3', value: "PIPELINEID-#{pipeline.id} and PROJECTID-$CI_PROJECT_ID" }
)
end
end
end
end

View File

@ -140,7 +140,7 @@ RSpec.describe Issues::CloseService do
end
context 'when the escalation status did not change to resolved' do
let(:escalation_status) { instance_double('IncidentManagement::IssuableEscalationStatus', resolve: false) }
let(:escalation_status) { instance_double('IncidentManagement::IssuableEscalationStatus', resolve: false, status_name: 'acknowledged') }
before do
allow(issue).to receive(:incident_management_issuable_escalation_status).and_return(escalation_status)

View File

@ -1168,6 +1168,7 @@ RSpec.describe Issues::UpdateService, :mailer do
it 'triggers webhooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :incident_hooks)
update_issue(opts)
end
@ -1281,6 +1282,7 @@ RSpec.describe Issues::UpdateService, :mailer do
it 'triggers webhooks' do
expect(project).to receive(:execute_hooks).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :issue_hooks)
expect(project).to receive(:execute_integrations).with(an_instance_of(Hash), :incident_hooks)
update_issue(opts)
end

View File

@ -337,11 +337,12 @@ RSpec.describe NotificationService, :mailer do
describe '#access_token_expired' do
let_it_be(:user) { create(:user) }
let_it_be(:pat) { create(:personal_access_token, user: user) }
subject { notification.access_token_expired(user) }
subject { notification.access_token_expired(user, pat.name) }
it 'sends email to the token owner' do
expect { subject }.to have_enqueued_email(user, mail: "access_token_expired_email")
expect { subject }.to have_enqueued_email(user, pat.name, mail: "access_token_expired_email")
end
context 'when user is not allowed to receive notifications' do
@ -350,7 +351,7 @@ RSpec.describe NotificationService, :mailer do
end
it 'does not send email to the token owner' do
expect { subject }.not_to have_enqueued_email(user, mail: "access_token_expired_email")
expect { subject }.not_to have_enqueued_email(user, pat.name, mail: "access_token_expired_email")
end
end
end

View File

@ -92,7 +92,7 @@ module CycleAnalyticsHelpers
end
def create_value_stream_group_aggregation(group)
aggregation = Analytics::CycleAnalytics::Aggregation.safe_create_for_group(group)
aggregation = Analytics::CycleAnalytics::Aggregation.safe_create_for_namespace(group)
Analytics::CycleAnalytics::AggregatorService.new(aggregation: aggregation).execute
end

View File

@ -0,0 +1,28 @@
# frozen_string_literal: true
RSpec.shared_examples 'value stream analytics namespace models' do
let(:factory_name) { nil }
context 'when ProjectNamespace is given' do
it 'is valid' do
project_namespace = create(:project_namespace)
model = build(factory_name, namespace: project_namespace)
expect(model).to be_valid
expect(model.save).to be(true)
expect(model.namespace).to eq(project_namespace)
end
end
context 'when Namespace is given' do
it 'fails' do
namespace = create(:namespace)
model = build(factory_name, namespace: namespace)
expect(model).to be_invalid
error_message = s_('CycleAnalytics|the assigned object is not supported')
expect(model.errors.messages_for(:namespace)).to eq([error_message])
end
end
end

View File

@ -25,7 +25,7 @@ RSpec.shared_examples 'value stream analytics stage' do
stage = described_class.new(valid_params.except(:parent))
expect(stage).to be_invalid
expect(stage.errors[parent_name]).to include("can't be blank")
expect(stage.errors[parent_name]).to include('must exist')
end
it 'validates presence of start_event_identifier' do

View File

@ -11,7 +11,7 @@ RSpec.describe PersonalAccessTokens::ExpiredNotificationWorker, type: :worker do
it 'uses notification service to send email to the user' do
expect_next_instance_of(NotificationService) do |notification_service|
expect(notification_service).to receive(:access_token_expired).with(expired_today.user)
expect(notification_service).to receive(:access_token_expired).with(expired_today.user, [expired_today.name])
end
worker.perform
@ -25,7 +25,7 @@ RSpec.describe PersonalAccessTokens::ExpiredNotificationWorker, type: :worker do
shared_examples 'expiry notification is not required to be sent for the token' do
it do
expect_next_instance_of(NotificationService) do |notification_service|
expect(notification_service).not_to receive(:access_token_expired).with(token.user)
expect(notification_service).not_to receive(:access_token_expired).with(token.user, [token.name])
end
worker.perform

View File

@ -83,24 +83,6 @@ RSpec.describe Projects::DeleteBranchWorker, feature_category: :source_code_mana
expect { worker.perform(project.id, user.id, branch) }.not_to raise_error
end
end
context 'when track_and_raise_delete_source_errors is disabled' do
let(:status_code) { 400 }
before do
stub_feature_flags(track_and_raise_delete_source_errors: false)
end
it 'does not track the exception' do
expect_next_instance_of(::Branches::DeleteService) do |instance|
expect(instance).to receive(:execute).with(branch).and_return(service_result)
end
expect(service_result).not_to receive(:track_and_raise_exception)
expect { worker.perform(project.id, user.id, branch) }.not_to raise_error
end
end
end
end

View File

@ -1047,7 +1047,7 @@
resolved "https://registry.yarnpkg.com/@csstools/selector-specificity/-/selector-specificity-2.0.1.tgz#b6b8d81780b9a9f6459f4bfe9226ac6aefaefe87"
integrity sha512-aG20vknL4/YjQF9BSV7ts4EWm/yrjagAN7OWBNmlbEOUiu0llj4OGrFoOKK3g2vey4/p2omKCoHrWtPxSwV3HA==
"@cubejs-client/core@^0.31.0":
"@cubejs-client/core@^0.31.15":
version "0.31.15"
resolved "https://registry.yarnpkg.com/@cubejs-client/core/-/core-0.31.15.tgz#db0ee90f5ba7f33a3fae6c81e5e13ab1cf2cd71b"
integrity sha512-VQqvvJn++nqO8aOr/dFtyUURNFYAlP3XlDiupiGLXmSsuUn0BuozJQAmJ5XxPPhvz5k9qBko7KkZuC6ikZTdcA==
@ -1059,6 +1059,15 @@
url-search-params-polyfill "^7.0.0"
uuid "^8.3.2"
"@cubejs-client/vue@^0.31.19":
version "0.31.19"
resolved "https://registry.yarnpkg.com/@cubejs-client/vue/-/vue-0.31.19.tgz#cd8f588e14046091b085c3405f28a62f3e4d949d"
integrity sha512-SY2+flIZJARp0cpS7e9ewYwaYCqqh8ifyc11S55ENo2KJsyzdLELSIiTDqy6hppXqtALSOE4feW4Eb/blh6e0w==
dependencies:
"@cubejs-client/core" "^0.31.15"
core-js "^3.6.5"
ramda "^0.27.2"
"@discoveryjs/json-ext@^0.5.0":
version "0.5.6"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f"