Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
2e9f877e8b
commit
980d813e90
|
|
@ -11,25 +11,25 @@
|
|||
if: '$CI_PROJECT_NAME != "gitlab-foss" && $CI_PROJECT_NAME != "gitlab-ce" && $CI_PROJECT_NAME != "gitlabhq"'
|
||||
|
||||
.if-default-refs: &if-default-refs
|
||||
if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG || $FORCE_GITLAB_CI'
|
||||
if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME == "main" || $CI_COMMIT_REF_NAME =~ /^[\d-]+-stable(-ee)?$/ || $CI_COMMIT_REF_NAME =~ /^\d+-\d+-auto-deploy-\d+$/ || $CI_COMMIT_REF_NAME =~ /^security\// || $CI_MERGE_REQUEST_IID || $CI_COMMIT_TAG || $FORCE_GITLAB_CI'
|
||||
|
||||
.if-master-refs: &if-master-refs
|
||||
if: '$CI_COMMIT_REF_NAME == "master"'
|
||||
if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME == "main"'
|
||||
|
||||
.if-master-push: &if-master-push
|
||||
if: '$CI_COMMIT_BRANCH == "master" && $CI_PIPELINE_SOURCE == "push"'
|
||||
if: '($CI_COMMIT_BRANCH == "master" || $CI_COMMIT_REF_NAME == "main") && $CI_PIPELINE_SOURCE == "push"'
|
||||
|
||||
.if-master-schedule-2-hourly: &if-master-schedule-2-hourly
|
||||
if: '$CI_COMMIT_BRANCH == "master" && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "2-hourly"'
|
||||
if: '($CI_COMMIT_BRANCH == "master" || $CI_COMMIT_REF_NAME == "main") && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "2-hourly"'
|
||||
|
||||
.if-master-schedule-nightly: &if-master-schedule-nightly
|
||||
if: '$CI_COMMIT_BRANCH == "master" && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "nightly"'
|
||||
if: '($CI_COMMIT_BRANCH == "master" || $CI_COMMIT_REF_NAME == "main") && $CI_PIPELINE_SOURCE == "schedule" && $FREQUENCY == "nightly"'
|
||||
|
||||
.if-auto-deploy-branches: &if-auto-deploy-branches
|
||||
if: '$CI_COMMIT_BRANCH =~ /^\d+-\d+-auto-deploy-\d+$/'
|
||||
|
||||
.if-master-or-tag: &if-master-or-tag
|
||||
if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_TAG'
|
||||
if: '$CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME == "main" || $CI_COMMIT_TAG'
|
||||
|
||||
.if-merge-request: &if-merge-request
|
||||
if: '$CI_MERGE_REQUEST_IID'
|
||||
|
|
@ -53,7 +53,7 @@
|
|||
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_PIPELINE_SOURCE == "schedule"'
|
||||
|
||||
.if-dot-com-gitlab-org-master: &if-dot-com-gitlab-org-master
|
||||
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_COMMIT_REF_NAME == "master"'
|
||||
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && ($CI_COMMIT_REF_NAME == "master" || $CI_COMMIT_REF_NAME == "main")'
|
||||
|
||||
.if-dot-com-gitlab-org-merge-request: &if-dot-com-gitlab-org-merge-request
|
||||
if: '$CI_SERVER_HOST == "gitlab.com" && $CI_PROJECT_NAMESPACE == "gitlab-org" && $CI_MERGE_REQUEST_IID'
|
||||
|
|
@ -445,7 +445,7 @@
|
|||
|
||||
.frontend:rules:bundle-size-review:
|
||||
rules:
|
||||
- if: '$DANGER_GITLAB_API_TOKEN && $CI_MERGE_REQUEST_IID && $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"'
|
||||
- if: '$DANGER_GITLAB_API_TOKEN && $CI_MERGE_REQUEST_IID && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "main")'
|
||||
changes: *frontend-patterns
|
||||
allow_failure: true
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
3f29772a7241e66cc89608778b2411f2a3733a17
|
||||
c45afa70f5bd9723f0836ff228a11bc896c45511
|
||||
|
|
|
|||
|
|
@ -86,6 +86,8 @@ export default {
|
|||
data-track-event="click_button"
|
||||
data-track-label="feature_flag_toggle"
|
||||
class="gl-mr-4"
|
||||
:label="__('Feature flag status')"
|
||||
label-position="hidden"
|
||||
@change="toggleActive"
|
||||
/>
|
||||
<h3 class="page-title gl-m-0">{{ title }}</h3>
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
canCreateGroup: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -171,6 +176,7 @@ export default {
|
|||
:key="group.id"
|
||||
:group="group"
|
||||
:available-namespaces="availableNamespaces"
|
||||
:can-create-group="canCreateGroup"
|
||||
@update-target-namespace="updateTargetNamespace(group.id, $event)"
|
||||
@update-new-name="updateNewName(group.id, $event)"
|
||||
@import-group="importGroup(group.id)"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { GlButton, GlIcon, GlLink, GlFormInput } from '@gitlab/ui';
|
||||
import { joinPaths } from '~/lib/utils/url_utility';
|
||||
import { s__ } from '~/locale';
|
||||
import Select2Select from '~/vue_shared/components/select2_select.vue';
|
||||
import ImportStatus from '../../components/import_status.vue';
|
||||
import { STATUSES } from '../../constants';
|
||||
|
|
@ -23,6 +24,11 @@ export default {
|
|||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
canCreateGroup: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isDisabled() {
|
||||
|
|
@ -34,11 +40,23 @@ export default {
|
|||
},
|
||||
|
||||
select2Options() {
|
||||
const availableNamespacesData = this.availableNamespaces.map((namespace) => ({
|
||||
id: namespace.full_path,
|
||||
text: namespace.full_path,
|
||||
}));
|
||||
|
||||
if (!this.canCreateGroup) {
|
||||
return { data: availableNamespacesData };
|
||||
}
|
||||
|
||||
return {
|
||||
data: this.availableNamespaces.map((namespace) => ({
|
||||
id: namespace.full_path,
|
||||
text: namespace.full_path,
|
||||
})),
|
||||
data: [
|
||||
{ id: '', text: s__('BulkImport|No parent') },
|
||||
{
|
||||
text: s__('BulkImport|Existing groups'),
|
||||
children: availableNamespacesData,
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import Vue from 'vue';
|
||||
import VueApollo from 'vue-apollo';
|
||||
import { parseBoolean } from '~/lib/utils/common_utils';
|
||||
import Translate from '~/vue_shared/translate';
|
||||
import ImportTable from './components/import_table.vue';
|
||||
import { createApolloClient } from './graphql/client_factory';
|
||||
|
|
@ -16,6 +17,7 @@ export function mountImportGroupsApp(mountElement) {
|
|||
createBulkImportPath,
|
||||
jobsPath,
|
||||
sourceUrl,
|
||||
canCreateGroup,
|
||||
} = mountElement.dataset;
|
||||
const apolloProvider = new VueApollo({
|
||||
defaultClient: createApolloClient({
|
||||
|
|
@ -35,6 +37,7 @@ export function mountImportGroupsApp(mountElement) {
|
|||
return createElement(ImportTable, {
|
||||
props: {
|
||||
sourceUrl,
|
||||
canCreateGroup: parseBoolean(canCreateGroup),
|
||||
},
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
*/
|
||||
|
||||
$mr-widget-min-height: 69px;
|
||||
$tabs-holder-z-index: 250;
|
||||
|
||||
.space-children {
|
||||
@include clearfix;
|
||||
|
|
@ -675,7 +676,7 @@ $mr-widget-min-height: 69px;
|
|||
|
||||
.mr-version-controls {
|
||||
position: relative;
|
||||
z-index: 203;
|
||||
z-index: $tabs-holder-z-index + 10;
|
||||
background: $white;
|
||||
color: $gl-text-color;
|
||||
margin-top: -1px;
|
||||
|
|
@ -745,7 +746,7 @@ $mr-widget-min-height: 69px;
|
|||
.merge-request-tabs-holder,
|
||||
.epic-tabs-holder {
|
||||
top: $header-height;
|
||||
z-index: 250;
|
||||
z-index: $tabs-holder-z-index;
|
||||
background-color: $body-bg;
|
||||
border-bottom: 1px solid $border-color;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
class HelpController < ApplicationController
|
||||
skip_before_action :authenticate_user!, unless: :public_visibility_restricted?
|
||||
skip_before_action :check_two_factor_requirement
|
||||
feature_category :not_owned
|
||||
|
||||
layout 'help'
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ class Projects::PipelinesController < Projects::ApplicationController
|
|||
before_action :authorize_update_pipeline!, only: [:retry, :cancel]
|
||||
before_action do
|
||||
push_frontend_feature_flag(:pipelines_security_report_summary, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: true)
|
||||
push_frontend_feature_flag(:new_pipeline_form, project, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:graphql_pipeline_details, project, type: :development, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:graphql_pipeline_details_users, current_user, type: :development, default_enabled: :yaml)
|
||||
push_frontend_feature_flag(:ci_mini_pipeline_gl_dropdown, project, type: :development, default_enabled: :yaml)
|
||||
|
|
|
|||
|
|
@ -37,8 +37,9 @@ class BulkImports::Entity < ApplicationRecord
|
|||
|
||||
validates :project, absence: true, if: :group
|
||||
validates :group, absence: true, if: :project
|
||||
validates :source_type, :source_full_path, :destination_name,
|
||||
:destination_namespace, presence: true
|
||||
validates :source_type, :source_full_path, :destination_name, presence: true
|
||||
validates :destination_namespace, exclusion: [nil], if: :group
|
||||
validates :destination_namespace, presence: true, if: :project
|
||||
|
||||
validate :validate_parent_is_a_group, if: :parent
|
||||
validate :validate_imported_entity_type
|
||||
|
|
|
|||
|
|
@ -438,6 +438,10 @@ class Issue < ApplicationRecord
|
|||
issue_type_supports?(:assignee)
|
||||
end
|
||||
|
||||
def email_participants_downcase
|
||||
issue_email_participants.pluck(IssueEmailParticipant.arel_table[:email].lower)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def ensure_metrics
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
class IssueEmailParticipant < ApplicationRecord
|
||||
belongs_to :issue
|
||||
|
||||
validates :email, presence: true, uniqueness: { scope: [:issue_id] }
|
||||
validates :email, uniqueness: { scope: [:issue_id], case_sensitive: false }
|
||||
validates :issue, presence: true
|
||||
validate :validate_email_format
|
||||
|
||||
|
|
|
|||
|
|
@ -14,5 +14,7 @@ module Clusters
|
|||
GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME = 'gitlab-crossplane-database-rolebinding'
|
||||
KNATIVE_SERVING_NAMESPACE = 'knative-serving'
|
||||
ISTIO_SYSTEM_NAMESPACE = 'istio-system'
|
||||
GITLAB_CILIUM_ROLE_NAME = 'gitlab-cilium-role'
|
||||
GITLAB_CILIUM_ROLE_BINDING_NAME = 'gitlab-cilium-rolebinding'
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -53,6 +53,8 @@ module Clusters
|
|||
create_or_update_knative_serving_role_binding
|
||||
create_or_update_crossplane_database_role
|
||||
create_or_update_crossplane_database_role_binding
|
||||
create_or_update_cilium_role
|
||||
create_or_update_cilium_role_binding
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -97,6 +99,14 @@ module Clusters
|
|||
kubeclient.update_role_binding(crossplane_database_role_binding_resource)
|
||||
end
|
||||
|
||||
def create_or_update_cilium_role
|
||||
kubeclient.update_role(cilium_role_resource)
|
||||
end
|
||||
|
||||
def create_or_update_cilium_role_binding
|
||||
kubeclient.update_role_binding(cilium_role_binding_resource)
|
||||
end
|
||||
|
||||
def service_account_resource
|
||||
Gitlab::Kubernetes::ServiceAccount.new(
|
||||
service_account_name,
|
||||
|
|
@ -175,6 +185,28 @@ module Clusters
|
|||
service_account_name: service_account_name
|
||||
).generate
|
||||
end
|
||||
|
||||
def cilium_role_resource
|
||||
Gitlab::Kubernetes::Role.new(
|
||||
name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME,
|
||||
namespace: service_account_namespace,
|
||||
rules: [{
|
||||
apiGroups: %w(cilium.io),
|
||||
resources: %w(ciliumnetworkpolicies),
|
||||
verbs: %w(get list create update patch)
|
||||
}]
|
||||
).generate
|
||||
end
|
||||
|
||||
def cilium_role_binding_resource
|
||||
Gitlab::Kubernetes::RoleBinding.new(
|
||||
name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME,
|
||||
role_name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME,
|
||||
role_kind: :Role,
|
||||
namespace: service_account_namespace,
|
||||
service_account_name: service_account_name
|
||||
).generate
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -241,6 +241,10 @@ module SystemNoteService
|
|||
::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).mark_canonical_issue_of_duplicate(duplicate_issue)
|
||||
end
|
||||
|
||||
def add_email_participants(noteable, project, author, body)
|
||||
::SystemNotes::IssuablesService.new(noteable: noteable, project: project, author: author).add_email_participants(body)
|
||||
end
|
||||
|
||||
def discussion_lock(issuable, author)
|
||||
::SystemNotes::IssuablesService.new(noteable: issuable, project: issuable.project, author: author).discussion_lock
|
||||
end
|
||||
|
|
|
|||
|
|
@ -354,6 +354,10 @@ module SystemNotes
|
|||
create_note(NoteSummary.new(noteable, project, author, body, action: 'duplicate'))
|
||||
end
|
||||
|
||||
def add_email_participants(body)
|
||||
create_note(NoteSummary.new(noteable, project, author, body))
|
||||
end
|
||||
|
||||
def discussion_lock
|
||||
action = noteable.discussion_locked? ? 'locked' : 'unlocked'
|
||||
body = "#{action} this #{noteable.class.to_s.titleize.downcase}"
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@
|
|||
available_namespaces_path: import_available_namespaces_path(format: :json),
|
||||
create_bulk_import_path: import_bulk_imports_path(format: :json),
|
||||
jobs_path: realtime_changes_import_bulk_imports_path(format: :json),
|
||||
can_create_group: current_user.can_create_group?.to_s,
|
||||
source_url: @source_url } }
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
= s_('Pipeline|Run Pipeline')
|
||||
%hr
|
||||
|
||||
- if Feature.enabled?(:new_pipeline_form, @project, default_enabled: true)
|
||||
- if Feature.enabled?(:new_pipeline_form, @project, default_enabled: :yaml)
|
||||
#js-new-pipeline{ data: { project_id: @project.id,
|
||||
pipelines_path: project_pipelines_path(@project),
|
||||
config_variables_path: config_variables_namespace_project_pipelines_path(@project.namespace, @project),
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@
|
|||
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
|
||||
- cache_key = project_list_cache_key(project, pipeline_status: pipeline_status)
|
||||
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
|
||||
- show_pipeline_status_icon = pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project) && project.last_pipeline.present?
|
||||
- show_pipeline_status_icon = pipeline_status && can?(current_user, :read_cross_project) && project.pipeline_status.has_status? && can?(current_user, :read_build, project)
|
||||
- last_pipeline = project.last_pipeline if show_pipeline_status_icon
|
||||
- css_controls_class = compact_mode ? [] : ["flex-lg-row", "justify-content-lg-between"]
|
||||
- css_controls_class << "with-pipeline-status" if show_pipeline_status_icon
|
||||
- css_controls_class << "with-pipeline-status" if show_pipeline_status_icon && last_pipeline.present?
|
||||
- avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar'
|
||||
|
||||
%li.project-row.d-flex{ class: css_class }
|
||||
|
|
@ -68,10 +69,10 @@
|
|||
|
||||
.controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0.text-secondary{ class: css_controls_class.join(" ") }
|
||||
.icon-container.d-flex.align-items-center
|
||||
- if show_pipeline_status_icon
|
||||
- if show_pipeline_status_icon && last_pipeline.present?
|
||||
- pipeline_path = pipelines_project_commit_path(project.pipeline_status.project, project.pipeline_status.sha, ref: project.pipeline_status.ref)
|
||||
%span.icon-wrapper.pipeline-status
|
||||
= render 'ci/status/icon', status: project.last_pipeline.detailed_status(current_user), tooltip_placement: 'top', path: pipeline_path
|
||||
= render 'ci/status/icon', status: last_pipeline.detailed_status(current_user), tooltip_placement: 'top', path: pipeline_path
|
||||
|
||||
= render_if_exists 'shared/projects/archived', project: project
|
||||
- if stars
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add invite_email Quick Action
|
||||
merge_request: 49264
|
||||
author: Lee Tickett
|
||||
type: added
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add GlToggle label in edit feature flag
|
||||
merge_request: 54546
|
||||
author: Yogi (@yo)
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Render version dropdowns in MR changes view above tab navbar
|
||||
merge_request: 54159
|
||||
author: Simon Stieger @sim0
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Fix rendering of projects when the last pipeline changes during rendering
|
||||
merge_request: 54651
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add Role and Rolebinding for CiliumNetworkPolicies
|
||||
merge_request: 54130
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Skip two factor setup for help pages
|
||||
merge_request: 54739
|
||||
author:
|
||||
type: fixed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Update LaTeX Docker image in CI Templates to TexLive 2020
|
||||
merge_request: 52043
|
||||
author: Michael Schmitt @schmitmd
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Allow importing groups as new top-level groups
|
||||
merge_request: 54323
|
||||
author:
|
||||
type: changed
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
name: issue_email_participants
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49264
|
||||
rollout_issue_url:
|
||||
milestone: '13.8'
|
||||
type: development
|
||||
group: group::product planning
|
||||
default_enabled: false
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
# See Usage Ping metrics dictionary docs https://docs.gitlab.com/ee/development/usage_ping/metrics_dictionary.html
|
||||
key_path: redis_hll_counters.quickactions.i_quickactions_invite_email_single_monthly
|
||||
description:
|
||||
product_section: dev
|
||||
product_stage: plan
|
||||
product_group: group::product planning
|
||||
product_category: issue_tracking
|
||||
value_type: number
|
||||
status: implemented
|
||||
milestone: 13.10
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49264
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
distribution:
|
||||
- ce
|
||||
tier:
|
||||
- free
|
||||
skip_validation: true
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
# See Usage Ping metrics dictionary docs https://docs.gitlab.com/ee/development/usage_ping/metrics_dictionary.html
|
||||
key_path: redis_hll_counters.quickactions.i_quickactions_invite_email_multiple_monthly
|
||||
description:
|
||||
product_section: dev
|
||||
product_stage: plan
|
||||
product_group: group::product planning
|
||||
product_category: issue_tracking
|
||||
value_type: number
|
||||
status: implemented
|
||||
milestone: 13.10
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49264
|
||||
time_frame: 28d
|
||||
data_source: redis_hll
|
||||
distribution:
|
||||
- ce
|
||||
tier:
|
||||
- free
|
||||
skip_validation: true
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class RecreateIndexIssueEmailParticipantsOnIssueIdAndEmail < ActiveRecord::Migration[6.0]
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
disable_ddl_transaction!
|
||||
|
||||
OLD_INDEX_NAME = 'index_issue_email_participants_on_issue_id_and_email'
|
||||
NEW_INDEX_NAME = 'index_issue_email_participants_on_issue_id_and_lower_email'
|
||||
|
||||
def up
|
||||
# This table is currently empty, so no need to worry about unique index violations
|
||||
add_concurrent_index :issue_email_participants, 'issue_id, lower(email)', unique: true, name: NEW_INDEX_NAME
|
||||
remove_concurrent_index_by_name :issue_email_participants, OLD_INDEX_NAME
|
||||
end
|
||||
|
||||
def down
|
||||
add_concurrent_index :issue_email_participants, [:issue_id, :email], unique: true, name: OLD_INDEX_NAME
|
||||
remove_concurrent_index_by_name :issue_email_participants, NEW_INDEX_NAME
|
||||
end
|
||||
end
|
||||
|
|
@ -0,0 +1 @@
|
|||
a7397b8f5d00b85f8c963f04ae062393b21313d97c9b9875a88a76608b98f826
|
||||
|
|
@ -22421,7 +22421,7 @@ CREATE UNIQUE INDEX index_issuable_slas_on_issue_id ON issuable_slas USING btree
|
|||
|
||||
CREATE INDEX index_issue_assignees_on_user_id ON issue_assignees USING btree (user_id);
|
||||
|
||||
CREATE UNIQUE INDEX index_issue_email_participants_on_issue_id_and_email ON issue_email_participants USING btree (issue_id, email);
|
||||
CREATE UNIQUE INDEX index_issue_email_participants_on_issue_id_and_lower_email ON issue_email_participants USING btree (issue_id, lower(email));
|
||||
|
||||
CREATE INDEX index_issue_links_on_source_id ON issue_links USING btree (source_id);
|
||||
|
||||
|
|
|
|||
|
|
@ -2965,6 +2965,7 @@ Information about pagination in a connection.
|
|||
| `path` | String | Relative path to the pipeline's page. |
|
||||
| `project` | Project | Project the pipeline belongs to. |
|
||||
| `retryable` | Boolean! | Specifies if a pipeline can be retried. |
|
||||
| `securityReportFindings` | PipelineSecurityReportFindingConnection | Vulnerability findings reported on the pipeline. |
|
||||
| `securityReportSummary` | SecurityReportSummary | Vulnerability and scanned resource counts for each security scanner of the pipeline. |
|
||||
| `sha` | String! | SHA of the pipeline's commit. |
|
||||
| `sourceJob` | CiJob | Job where pipeline was triggered from. |
|
||||
|
|
@ -3029,6 +3030,25 @@ Autogenerated return type of PipelineRetry.
|
|||
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
|
||||
| `pipeline` | Pipeline | The pipeline after mutation. |
|
||||
|
||||
### PipelineSecurityReportFinding
|
||||
|
||||
Represents vulnerability finding of a security report on the pipeline.
|
||||
|
||||
| Field | Type | Description |
|
||||
| ----- | ---- | ----------- |
|
||||
| `confidence` | String | Type of the security report that found the vulnerability. |
|
||||
| `description` | String | Description of the vulnerability finding. |
|
||||
| `identifiers` | VulnerabilityIdentifier! => Array | Identifiers of the vulnerabilit finding. |
|
||||
| `location` | VulnerabilityLocation | Location metadata for the vulnerability. Its fields depend on the type of security scan that found the vulnerability. |
|
||||
| `name` | String | Name of the vulnerability finding. |
|
||||
| `project` | Project | The project on which the vulnerability finding was found. |
|
||||
| `projectFingerprint` | String | Name of the vulnerability finding. |
|
||||
| `reportType` | VulnerabilityReportType | Type of the security report that found the vulnerability finding. |
|
||||
| `scanner` | VulnerabilityScanner | Scanner metadata for the vulnerability. |
|
||||
| `severity` | VulnerabilitySeverity | Severity of the vulnerability finding. |
|
||||
| `solution` | String | URL to the vulnerability's details page. |
|
||||
| `uuid` | String | Name of the vulnerability finding. |
|
||||
|
||||
### Project
|
||||
|
||||
| Field | Type | Description |
|
||||
|
|
|
|||
|
|
@ -704,13 +704,13 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
|
|||
| `if-not-canonical-namespace` | Matches if the project isn't in the canonical (`gitlab-org/`) or security (`gitlab-org/security`) namespace. | Use to create a job for forks (by using `when: on_success|manual`), or **not** create a job for forks (by using `when: never`). |
|
||||
| `if-not-ee` | Matches if the project isn't EE (i.e. project name isn't `gitlab` or `gitlab-ee`). | Use to create a job only in the FOSS project (by using `when: on_success|manual`), or **not** create a job if the project is EE (by using `when: never`). |
|
||||
| `if-not-foss` | Matches if the project isn't FOSS (i.e. project name isn't `gitlab-foss`, `gitlab-ce`, or `gitlabhq`). | Use to create a job only in the EE project (by using `when: on_success|manual`), or **not** create a job if the project is FOSS (by using `when: never`). |
|
||||
| `if-default-refs` | Matches if the pipeline is for `master`, `/^[\d-]+-stable(-ee)?$/` (stable branches), `/^\d+-\d+-auto-deploy-\d+$/` (auto-deploy branches), `/^security\//` (security branches), merge requests, and tags. | Note that jobs aren't created for branches with this default configuration. |
|
||||
| `if-master-refs` | Matches if the current branch is `master`. | |
|
||||
| `if-master-push` | Matches if the current branch is `master` and pipeline source is `push`. | |
|
||||
| `if-master-schedule-2-hourly` | Matches if the current branch is `master` and pipeline runs on a 2-hourly schedule. | |
|
||||
| `if-master-schedule-nightly` | Matches if the current branch is `master` and pipeline runs on a nightly schedule. | |
|
||||
| `if-default-refs` | Matches if the pipeline is for `master`, `main`, `/^[\d-]+-stable(-ee)?$/` (stable branches), `/^\d+-\d+-auto-deploy-\d+$/` (auto-deploy branches), `/^security\//` (security branches), merge requests, and tags. | Note that jobs aren't created for branches with this default configuration. |
|
||||
| `if-master-refs` | Matches if the current branch is `master` or `main`. | |
|
||||
| `if-master-push` | Matches if the current branch is `master` or `main` and pipeline source is `push`. | |
|
||||
| `if-master-schedule-2-hourly` | Matches if the current branch is `master` or `main` and pipeline runs on a 2-hourly schedule. | |
|
||||
| `if-master-schedule-nightly` | Matches if the current branch is `master` or `main` and pipeline runs on a nightly schedule. | |
|
||||
| `if-auto-deploy-branches` | Matches if the current branch is an auto-deploy one. | |
|
||||
| `if-master-or-tag` | Matches if the pipeline is for the `master` branch or for a tag. | |
|
||||
| `if-master-or-tag` | Matches if the pipeline is for the `master` or `main` branch or for a tag. | |
|
||||
| `if-merge-request` | Matches if the pipeline is for a merge request. | |
|
||||
| `if-merge-request-title-as-if-foss` | Matches if the pipeline is for a merge request and the MR title includes "RUN AS-IF-FOSS". | |
|
||||
| `if-merge-request-title-update-caches` | Matches if the pipeline is for a merge request and the MR title includes "UPDATE CACHE". | |
|
||||
|
|
@ -719,7 +719,7 @@ and included in `rules` definitions via [YAML anchors](../ci/yaml/README.md#anch
|
|||
| `if-security-schedule` | Matches if the pipeline is for a security scheduled pipeline. | |
|
||||
| `if-nightly-master-schedule` | Matches if the pipeline is for a `master` scheduled pipeline with `$NIGHTLY` set. | |
|
||||
| `if-dot-com-gitlab-org-schedule` | Limits jobs creation to scheduled pipelines for the `gitlab-org` group on GitLab.com. | |
|
||||
| `if-dot-com-gitlab-org-master` | Limits jobs creation to the `master` branch for the `gitlab-org` group on GitLab.com. | |
|
||||
| `if-dot-com-gitlab-org-master` | Limits jobs creation to the `master` or `main` branch for the `gitlab-org` group on GitLab.com. | |
|
||||
| `if-dot-com-gitlab-org-merge-request` | Limits jobs creation to merge requests for the `gitlab-org` group on GitLab.com. | |
|
||||
| `if-dot-com-gitlab-org-and-security-tag` | Limits job creation to tags for the `gitlab-org` and `gitlab-org/security` groups on GitLab.com. | |
|
||||
| `if-dot-com-gitlab-org-and-security-merge-request` | Limit jobs creation to merge requests for the `gitlab-org` and `gitlab-org/security` groups on GitLab.com. | |
|
||||
|
|
|
|||
|
|
@ -17641,6 +17641,48 @@ Missing description
|
|||
| `tier` | |
|
||||
| `skip_validation` | true |
|
||||
|
||||
## `redis_hll_counters.quickactions.i_quickactions_invite_email_multiple_monthly`
|
||||
|
||||
Missing description
|
||||
|
||||
| field | value |
|
||||
| --- | --- |
|
||||
| `key_path` | **`redis_hll_counters.quickactions.i_quickactions_invite_email_multiple_monthly`** |
|
||||
| `product_section` | dev |
|
||||
| `product_stage` | plan |
|
||||
| `product_group` | `group::product planning` |
|
||||
| `product_category` | `issue_tracking` |
|
||||
| `value_type` | number |
|
||||
| `status` | implemented |
|
||||
| `milestone` | 13.1 |
|
||||
| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49264) |
|
||||
| `time_frame` | 28d |
|
||||
| `data_source` | Redis_hll |
|
||||
| `distribution` | ce |
|
||||
| `tier` | free |
|
||||
| `skip_validation` | true |
|
||||
|
||||
## `redis_hll_counters.quickactions.i_quickactions_invite_email_single_monthly`
|
||||
|
||||
Missing description
|
||||
|
||||
| field | value |
|
||||
| --- | --- |
|
||||
| `key_path` | **`redis_hll_counters.quickactions.i_quickactions_invite_email_single_monthly`** |
|
||||
| `product_section` | dev |
|
||||
| `product_stage` | plan |
|
||||
| `product_group` | `group::product planning` |
|
||||
| `product_category` | `issue_tracking` |
|
||||
| `value_type` | number |
|
||||
| `status` | implemented |
|
||||
| `milestone` | 13.1 |
|
||||
| `introduced_by_url` | [Introduced by](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49264) |
|
||||
| `time_frame` | 28d |
|
||||
| `data_source` | Redis_hll |
|
||||
| `distribution` | ce |
|
||||
| `tier` | free |
|
||||
| `skip_validation` | true |
|
||||
|
||||
## `redis_hll_counters.quickactions.i_quickactions_iteration_monthly`
|
||||
|
||||
Missing description
|
||||
|
|
@ -19463,7 +19505,7 @@ Calculated unique users to perform Basic or Advanced searches by week
|
|||
|
||||
## `redis_hll_counters.search.search_total_unique_counts_monthly`
|
||||
|
||||
Calculated unique users to perform Basic or Advanced searches by month
|
||||
Total unique users for i_search_total, i_search_advanced, i_search_paid for recent 28 days. This metric is redundant because advanced will be a subset of paid and paid will be a subset of total. i_search_total is more appropriate if you just want the total
|
||||
|
||||
| field | value |
|
||||
| --- | --- |
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ The following quick actions are applicable to descriptions, discussions and thre
|
|||
| `/duplicate <#issue>` | ✓ | | | Close this issue and mark as a duplicate of another issue. **(FREE)** Also, mark both as related. **(STARTER)** |
|
||||
| `/epic <epic>` | ✓ | | | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. **(PREMIUM)** |
|
||||
| `/estimate <<W>w <DD>d <hh>h <mm>m>` | ✓ | ✓ | | Set time estimate. For example, `/estimate 1w 3d 2h 14m`. |
|
||||
| `/invite_email email1 email2` | ✓ | | | Add up to 6 e-mail participants. This action is behind feature flag `issue_email_participants` |
|
||||
| `/iteration *iteration:"iteration name"` | ✓ | | | Set iteration. For example, to set the `Late in July` iteration: `/iteration *iteration:"Late in July"` ([introduced in GitLab 13.1](https://gitlab.com/gitlab-org/gitlab/-/issues/196795)). **(STARTER)** |
|
||||
| `/label ~label1 ~label2` | ✓ | ✓ | ✓ | Add one or more labels. Label names can also start without a tilde (`~`), but mixed syntax is not supported. |
|
||||
| `/lock` | ✓ | ✓ | | Lock the discussions. |
|
||||
|
|
|
|||
|
|
@ -35,12 +35,11 @@ module BulkImports
|
|||
end
|
||||
|
||||
def transform_parent(context, import_entity, data)
|
||||
current_user = context.current_user
|
||||
namespace = Namespace.find_by_full_path(import_entity.destination_namespace)
|
||||
unless import_entity.destination_namespace.blank?
|
||||
namespace = Namespace.find_by_full_path(import_entity.destination_namespace)
|
||||
data['parent_id'] = namespace.id
|
||||
end
|
||||
|
||||
return data if namespace == current_user.namespace
|
||||
|
||||
data['parent_id'] = namespace.id
|
||||
data
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,20 @@
|
|||
# use docker image with latex preinstalled
|
||||
# since there is no official latex image, use https://github.com/blang/latex-docker
|
||||
# possible alternative: https://github.com/natlownes/docker-latex
|
||||
image: blang/latex
|
||||
---
|
||||
variables:
|
||||
# Feel free to choose the image that suits you best.
|
||||
# blang/latex:latest ... Former image used in this template. No longer maintained by author.
|
||||
# listx/texlive:2020 ... The default, referring to TexLive 2020. Current at least to 2021-02-02.
|
||||
|
||||
# Additional alternatives with high Docker pull counts:
|
||||
# thomasweise/docker-texlive-full
|
||||
# thomasweise/texlive
|
||||
# adnrv/texlive
|
||||
LATEX_IMAGE: listx/texlive:2020
|
||||
|
||||
build:
|
||||
image: $LATEX_IMAGE
|
||||
script:
|
||||
- latexmk -pdf
|
||||
|
||||
artifacts:
|
||||
paths:
|
||||
- "*.pdf"
|
||||
|
|
|
|||
|
|
@ -8,8 +8,11 @@ module Gitlab
|
|||
include TSort
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
def initialize(variables, project)
|
||||
@variables = variables
|
||||
def initialize(collection, project)
|
||||
raise(ArgumentError, "A Gitlab::Ci::Variables::Collection object was expected") unless
|
||||
collection.is_a?(Collection)
|
||||
|
||||
@collection = collection
|
||||
@project = project
|
||||
end
|
||||
|
||||
|
|
@ -35,16 +38,16 @@ module Gitlab
|
|||
# sort sorts an array of variables, ignoring unknown variable references.
|
||||
# If a circular variable reference is found, the original array is returned
|
||||
def sort
|
||||
return @variables if Feature.disabled?(:variable_inside_variable, @project)
|
||||
return @variables if errors
|
||||
return @collection if Feature.disabled?(:variable_inside_variable, @project)
|
||||
return @collection if errors
|
||||
|
||||
tsort
|
||||
Gitlab::Ci::Variables::Collection.new(tsort)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def tsort_each_node(&block)
|
||||
@variables.each(&block)
|
||||
@collection.each(&block)
|
||||
end
|
||||
|
||||
def tsort_each_child(variable, &block)
|
||||
|
|
@ -53,7 +56,7 @@ module Gitlab
|
|||
|
||||
def input_vars
|
||||
strong_memoize(:input_vars) do
|
||||
@variables.index_by { |env| env.fetch(:key) }
|
||||
@collection.index_by { |env| env[:key] }
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -235,6 +235,35 @@ module Gitlab
|
|||
@execution_message[:remove_zoom] = result.message
|
||||
end
|
||||
|
||||
desc _('Add email participant(s)')
|
||||
explanation _('Adds email participant(s)')
|
||||
params 'email1@example.com email2@example.com (up to 6 emails)'
|
||||
types Issue
|
||||
condition do
|
||||
Feature.enabled?(:issue_email_participants, parent) &&
|
||||
current_user.can?(:"admin_#{quick_action_target.to_ability_name}", quick_action_target)
|
||||
end
|
||||
command :invite_email do |emails = ""|
|
||||
MAX_NUMBER_OF_EMAILS = 6
|
||||
|
||||
existing_emails = quick_action_target.email_participants_downcase
|
||||
emails_to_add = emails.split(' ').index_by { |email| [email.downcase, email] }.except(*existing_emails).each_value.first(MAX_NUMBER_OF_EMAILS)
|
||||
added_emails = []
|
||||
|
||||
emails_to_add.each do |email|
|
||||
new_participant = quick_action_target.issue_email_participants.create(email: email)
|
||||
added_emails << email if new_participant.persisted?
|
||||
end
|
||||
|
||||
if added_emails.any?
|
||||
message = _("added %{emails}") % { emails: added_emails.to_sentence }
|
||||
SystemNoteService.add_email_participants(quick_action_target, quick_action_target.project, current_user, message)
|
||||
@execution_message[:invite_email] = message.upcase_first << "."
|
||||
else
|
||||
@execution_message[:invite_email] = _("No email participants were added. Either none were provided, or they already exist.")
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def zoom_link_service
|
||||
|
|
|
|||
|
|
@ -324,3 +324,13 @@
|
|||
redis_slot: quickactions
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_quickactions
|
||||
- name: i_quickactions_invite_email_single
|
||||
category: quickactions
|
||||
redis_slot: quickactions
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_quickactions
|
||||
- name: i_quickactions_invite_email_multiple
|
||||
category: quickactions
|
||||
redis_slot: quickactions
|
||||
aggregation: weekly
|
||||
feature_flag: usage_data_track_quickactions
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ module Gitlab
|
|||
event_name_for_unassign(args)
|
||||
when 'unlabel', 'remove_label'
|
||||
event_name_for_unlabel(args)
|
||||
when 'invite_email'
|
||||
'invite_email' + event_name_quantifier(args.split)
|
||||
else
|
||||
name
|
||||
end
|
||||
|
|
@ -44,10 +46,8 @@ module Gitlab
|
|||
|
||||
if args.count == 1 && args.first == 'me'
|
||||
'assign_self'
|
||||
elsif args.count == 1
|
||||
'assign_single'
|
||||
else
|
||||
'assign_multiple'
|
||||
'assign' + event_name_quantifier(args)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -82,6 +82,14 @@ module Gitlab
|
|||
'unlabel_all'
|
||||
end
|
||||
end
|
||||
|
||||
def event_name_quantifier(args)
|
||||
if args.count == 1
|
||||
'_single'
|
||||
else
|
||||
'_multiple'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1838,6 +1838,9 @@ msgstr ""
|
|||
msgid "Add email address"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add email participant(s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add environment"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -1997,6 +2000,9 @@ msgstr ""
|
|||
msgid "Adds an issue to an epic."
|
||||
msgstr ""
|
||||
|
||||
msgid "Adds email participant(s)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Adjust your filters/search criteria above. If you believe this may be an error, please refer to the %{linkStart}Geo Troubleshooting%{linkEnd} documentation for more information."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -5069,6 +5075,9 @@ msgstr ""
|
|||
msgid "Bulk update"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|Existing groups"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|From source group"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -5078,6 +5087,9 @@ msgstr ""
|
|||
msgid "BulkImport|Importing the group failed"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|No parent"
|
||||
msgstr ""
|
||||
|
||||
msgid "BulkImport|Showing %{start}-%{end} of %{total} from %{link}"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -7596,12 +7608,24 @@ msgstr ""
|
|||
msgid "ComplianceFrameworks|Combines with the CI configuration at runtime."
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceFrameworks|Compliance framework deleted successfully"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceFrameworks|Compliance pipeline configuration location (optional)"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceFrameworks|Could not find this configuration location, please try a different location"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceFrameworks|Delete compliance framework %{framework}"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceFrameworks|Delete framework"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceFrameworks|Error deleting the compliance framework. Please try again"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceFrameworks|Error fetching compliance frameworks data. Please refresh the page"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -7623,6 +7647,9 @@ msgstr ""
|
|||
msgid "ComplianceFrameworks|Use %{codeStart}::%{codeEnd} to create a %{linkStart}scoped set%{linkEnd} (eg. %{codeStart}SOX::AWS%{codeEnd})"
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceFrameworks|You are about to permanently delete the compliance framework %{framework} from all projects which currently have it applied, which may remove other functionality. This cannot be undone."
|
||||
msgstr ""
|
||||
|
||||
msgid "ComplianceFrameworks|e.g. include-gitlab.ci.yml@group-name/project-name"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -12581,6 +12608,9 @@ msgstr ""
|
|||
msgid "Feature flag is not enabled on the environment's project."
|
||||
msgstr ""
|
||||
|
||||
msgid "Feature flag status"
|
||||
msgstr ""
|
||||
|
||||
msgid "Feature flag was not removed."
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -20184,6 +20214,9 @@ msgstr ""
|
|||
msgid "No due date"
|
||||
msgstr ""
|
||||
|
||||
msgid "No email participants were added. Either none were provided, or they already exist."
|
||||
msgstr ""
|
||||
|
||||
msgid "No endpoint provided"
|
||||
msgstr ""
|
||||
|
||||
|
|
@ -34496,6 +34529,9 @@ msgstr ""
|
|||
msgid "added %{created_at_timeago}"
|
||||
msgstr ""
|
||||
|
||||
msgid "added %{emails}"
|
||||
msgstr ""
|
||||
|
||||
msgid "added a Zoom call to this issue"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
|||
|
|
@ -132,6 +132,18 @@ RSpec.describe HelpController do
|
|||
expect(response).to redirect_to(new_user_session_path)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when two factor is required' do
|
||||
before do
|
||||
stub_two_factor_required
|
||||
end
|
||||
|
||||
it 'does not redirect to two factor auth' do
|
||||
get :index
|
||||
|
||||
expect(response).not_to redirect_to(profile_two_factor_auth_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #show' do
|
||||
|
|
@ -152,6 +164,16 @@ RSpec.describe HelpController do
|
|||
end
|
||||
|
||||
it_behaves_like 'documentation pages local render'
|
||||
|
||||
context 'when two factor is required' do
|
||||
before do
|
||||
stub_two_factor_required
|
||||
end
|
||||
|
||||
it 'does not redirect to two factor auth' do
|
||||
expect(response).not_to redirect_to(profile_two_factor_auth_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a custom help_page_documentation_url is set in database' do
|
||||
|
|
@ -254,4 +276,9 @@ RSpec.describe HelpController do
|
|||
def stub_readme(content)
|
||||
expect_file_read(Rails.root.join('doc', 'README.md'), content: content)
|
||||
end
|
||||
|
||||
def stub_two_factor_required
|
||||
allow(controller).to receive(:two_factor_authentication_required?).and_return(true)
|
||||
allow(controller).to receive(:current_user_requires_two_factor?).and_return(true)
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -102,7 +102,12 @@ RSpec.describe 'Database schema' do
|
|||
context 'all foreign keys' do
|
||||
# for index to be effective, the FK constraint has to be at first place
|
||||
it 'are indexed' do
|
||||
first_indexed_column = indexes.map(&:columns).map(&:first)
|
||||
first_indexed_column = indexes.map(&:columns).map do |columns|
|
||||
# In cases of complex composite indexes, a string is returned eg:
|
||||
# "lower((extern_uid)::text), group_id"
|
||||
columns = columns.split(',') if columns.is_a?(String)
|
||||
columns.first.chomp
|
||||
end
|
||||
foreign_keys_columns = foreign_keys.map(&:column)
|
||||
|
||||
# Add the primary key column to the list of indexed columns because
|
||||
|
|
|
|||
|
|
@ -5,6 +5,41 @@ require 'spec_helper'
|
|||
RSpec.describe 'factories' do
|
||||
include Database::DatabaseHelpers
|
||||
|
||||
def skipped_traits
|
||||
[
|
||||
[:alert_management_alert, :with_ended_at],
|
||||
[:audit_event, :unauthenticated],
|
||||
[:ci_build_trace_chunk, :fog_with_data],
|
||||
[:ci_job_artifact, :remote_store],
|
||||
[:ci_job_artifact, :raw],
|
||||
[:ci_job_artifact, :gzip],
|
||||
[:ci_job_artifact, :correct_checksum],
|
||||
[:design_version, :empty],
|
||||
[:environment, :non_playable],
|
||||
[:go_module_commit, :files],
|
||||
[:go_module_commit, :package],
|
||||
[:go_module_version, :pseudo],
|
||||
[:composer_cache_file, :object_storage],
|
||||
[:debian_project_component_file, :object_storage],
|
||||
[:debian_project_distribution, :object_storage],
|
||||
[:debian_file_metadatum, :unknown],
|
||||
[:package_file, :object_storage],
|
||||
[:pages_domain, :without_certificate],
|
||||
[:pages_domain, :without_key],
|
||||
[:pages_domain, :with_missing_chain],
|
||||
[:pages_domain, :with_trusted_chain],
|
||||
[:pages_domain, :with_trusted_expired_chain],
|
||||
[:pages_domain, :explicit_ecdsa],
|
||||
[:project_member, :blocked],
|
||||
[:project, :remote_mirror],
|
||||
[:prometheus_alert_event, :none],
|
||||
[:remote_mirror, :ssh],
|
||||
[:self_managed_prometheus_alert_event, :resolved],
|
||||
[:self_managed_prometheus_alert_event, :none],
|
||||
[:user_preference, :only_comments]
|
||||
]
|
||||
end
|
||||
|
||||
shared_examples 'factory' do |factory|
|
||||
describe "#{factory.name} factory" do
|
||||
it 'does not raise error when built' do
|
||||
|
|
@ -16,8 +51,10 @@ RSpec.describe 'factories' do
|
|||
end
|
||||
|
||||
factory.definition.defined_traits.map(&:name).each do |trait_name|
|
||||
describe "linting #{trait_name} trait" do
|
||||
skip 'does not raise error when created' do
|
||||
describe "linting :#{trait_name} trait" do
|
||||
it 'does not raise error when created' do
|
||||
pending("Trait skipped linting due to legacy error") if skipped_traits.include?([factory.name, trait_name.to_sym])
|
||||
|
||||
expect { create(factory.name, trait_name) }.not_to raise_error
|
||||
end
|
||||
end
|
||||
|
|
@ -37,8 +74,13 @@ RSpec.describe 'factories' do
|
|||
import_state
|
||||
namespace
|
||||
project_broken_repo
|
||||
prometheus_alert
|
||||
prometheus_alert_event
|
||||
prometheus_metric
|
||||
self_managed_prometheus_alert_event
|
||||
users_star_project
|
||||
wiki_page
|
||||
wiki_page_meta
|
||||
].to_set.freeze
|
||||
|
||||
# Some factories and their corresponding models are based on
|
||||
|
|
|
|||
|
|
@ -150,5 +150,12 @@ describe('Edit feature flag form', () => {
|
|||
label: 'feature_flag_toggle',
|
||||
});
|
||||
});
|
||||
|
||||
it('should render the toggle with a visually hidden label', () => {
|
||||
expect(wrapper.find(GlToggle).props()).toMatchObject({
|
||||
label: 'Feature flag status',
|
||||
labelPosition: 'hidden',
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -75,6 +75,33 @@ describe('import table row', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('renders only namespaces if user cannot create new group', () => {
|
||||
createComponent({
|
||||
canCreateGroup: false,
|
||||
group: getFakeGroup(STATUSES.NONE),
|
||||
});
|
||||
|
||||
const dropdownData = findNamespaceDropdown().props().options.data;
|
||||
const noParentOption = dropdownData.find((o) => o.text === 'No parent');
|
||||
|
||||
expect(noParentOption).toBeUndefined();
|
||||
expect(dropdownData).toHaveLength(availableNamespacesFixture.length);
|
||||
});
|
||||
|
||||
it('renders no parent option in available namespaces if user can create new group', () => {
|
||||
createComponent({
|
||||
canCreateGroup: true,
|
||||
group: getFakeGroup(STATUSES.NONE),
|
||||
});
|
||||
|
||||
const dropdownData = findNamespaceDropdown().props().options.data;
|
||||
const noParentOption = dropdownData.find((o) => o.text === 'No parent');
|
||||
const existingGroupOption = dropdownData.find((o) => o.text === 'Existing groups');
|
||||
|
||||
expect(noParentOption.id).toBe('');
|
||||
expect(existingGroupOption.children).toHaveLength(availableNamespacesFixture.length);
|
||||
});
|
||||
|
||||
describe('when entity status is SCHEDULING', () => {
|
||||
beforeEach(() => {
|
||||
group = getFakeGroup(STATUSES.SCHEDULING);
|
||||
|
|
|
|||
|
|
@ -21,9 +21,13 @@ describe('import table', () => {
|
|||
let apolloProvider;
|
||||
|
||||
const FAKE_GROUP = generateFakeEntry({ id: 1, status: STATUSES.NONE });
|
||||
const FAKE_GROUPS = [
|
||||
generateFakeEntry({ id: 1, status: STATUSES.NONE }),
|
||||
generateFakeEntry({ id: 2, status: STATUSES.FINISHED }),
|
||||
];
|
||||
const FAKE_PAGE_INFO = { page: 1, perPage: 20, total: 40, totalPages: 2 };
|
||||
|
||||
const createComponent = ({ bulkImportSourceGroups }) => {
|
||||
const createComponent = ({ bulkImportSourceGroups, canCreateGroup }) => {
|
||||
apolloProvider = createMockApollo([], {
|
||||
Query: {
|
||||
availableNamespaces: () => availableNamespacesFixture,
|
||||
|
|
@ -39,6 +43,7 @@ describe('import table', () => {
|
|||
wrapper = shallowMount(ImportTable, {
|
||||
propsData: {
|
||||
sourceUrl: 'https://demo.host',
|
||||
canCreateGroup,
|
||||
},
|
||||
stubs: {
|
||||
GlSprintf,
|
||||
|
|
@ -84,10 +89,6 @@ describe('import table', () => {
|
|||
});
|
||||
|
||||
it('renders import row for each group in response', async () => {
|
||||
const FAKE_GROUPS = [
|
||||
generateFakeEntry({ id: 1, status: STATUSES.NONE }),
|
||||
generateFakeEntry({ id: 2, status: STATUSES.FINISHED }),
|
||||
];
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => ({
|
||||
nodes: FAKE_GROUPS,
|
||||
|
|
@ -99,6 +100,25 @@ describe('import table', () => {
|
|||
expect(wrapper.findAll(ImportTableRow)).toHaveLength(FAKE_GROUPS.length);
|
||||
});
|
||||
|
||||
it.each`
|
||||
canCreateGroup | userPermissions
|
||||
${true} | ${'user can create new top-level group'}
|
||||
${false} | ${'user cannot create new top-level group'}
|
||||
`('correctly passes canCreateGroup to rows when $userPermissions', async ({ canCreateGroup }) => {
|
||||
createComponent({
|
||||
bulkImportSourceGroups: () => ({
|
||||
nodes: FAKE_GROUPS,
|
||||
pageInfo: FAKE_PAGE_INFO,
|
||||
}),
|
||||
canCreateGroup,
|
||||
});
|
||||
await waitForPromises();
|
||||
|
||||
wrapper.findAllComponents(ImportTableRow).wrappers.forEach((w) => {
|
||||
expect(w.props().canCreateGroup).toBe(canCreateGroup);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not render status string when result list is empty', async () => {
|
||||
createComponent({
|
||||
bulkImportSourceGroups: jest.fn().mockResolvedValue({
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ RSpec.describe Types::Ci::PipelineType do
|
|||
]
|
||||
|
||||
if Gitlab.ee?
|
||||
expected_fields << 'security_report_summary'
|
||||
expected_fields += %w[security_report_summary security_report_findings]
|
||||
end
|
||||
|
||||
expect(described_class).to have_graphql_fields(*expected_fields)
|
||||
|
|
|
|||
|
|
@ -80,14 +80,14 @@ RSpec.describe BulkImports::Groups::Transformers::GroupAttributesTransformer do
|
|||
expect(transformed_data['parent_id']).to eq(parent.id)
|
||||
end
|
||||
|
||||
context 'when destination namespace is user namespace' do
|
||||
context 'when destination namespace is empty' do
|
||||
it 'does not set parent id' do
|
||||
entity = create(
|
||||
:bulk_import_entity,
|
||||
bulk_import: bulk_import,
|
||||
source_full_path: 'source/full/path',
|
||||
destination_name: group.name,
|
||||
destination_namespace: user.namespace.full_path
|
||||
destination_namespace: ''
|
||||
)
|
||||
context = BulkImports::Pipeline::Context.new(entity)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,31 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Gitlab::Ci::Variables::Collection::Sorted do
|
||||
describe '#initialize with non-Collection value' do
|
||||
let_it_be(:project_with_flag_disabled) { create(:project) }
|
||||
let_it_be(:project_with_flag_enabled) { create(:project) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(variable_inside_variable: [project_with_flag_enabled])
|
||||
end
|
||||
|
||||
context 'when FF :variable_inside_variable is disabled' do
|
||||
subject { Gitlab::Ci::Variables::Collection::Sorted.new([], project_with_flag_disabled) }
|
||||
|
||||
it 'raises ArgumentError' do
|
||||
expect { subject }.to raise_error(ArgumentError, /Collection object was expected/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when FF :variable_inside_variable is enabled' do
|
||||
subject { Gitlab::Ci::Variables::Collection::Sorted.new([], project_with_flag_enabled) }
|
||||
|
||||
it 'raises ArgumentError' do
|
||||
expect { subject }.to raise_error(ArgumentError, /Collection object was expected/)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#errors' do
|
||||
context 'when FF :variable_inside_variable is disabled' do
|
||||
let_it_be(:project_with_flag_disabled) { create(:project) }
|
||||
|
|
@ -56,7 +81,9 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sorted do
|
|||
end
|
||||
|
||||
with_them do
|
||||
subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables, project_with_flag_disabled) }
|
||||
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
|
||||
|
||||
subject { Gitlab::Ci::Variables::Collection::Sorted.new(collection, project_with_flag_disabled) }
|
||||
|
||||
it 'does not report error' do
|
||||
expect(subject.errors).to eq(nil)
|
||||
|
|
@ -106,7 +133,9 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sorted do
|
|||
end
|
||||
|
||||
with_them do
|
||||
subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables, project_with_flag_enabled) }
|
||||
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
|
||||
|
||||
subject { Gitlab::Ci::Variables::Collection::Sorted.new(collection, project_with_flag_enabled) }
|
||||
|
||||
it 'errors matches expected validation result' do
|
||||
expect(subject.errors).to eq(validation_result)
|
||||
|
|
@ -171,10 +200,12 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sorted do
|
|||
|
||||
with_them do
|
||||
let_it_be(:project) { create(:project) }
|
||||
subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables, project) }
|
||||
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
|
||||
|
||||
subject { Gitlab::Ci::Variables::Collection::Sorted.new(collection, project).sort }
|
||||
|
||||
it 'does not expand variables' do
|
||||
expect(subject.sort).to eq(variables)
|
||||
is_expected.to be(collection)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
@ -247,10 +278,12 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Sorted do
|
|||
|
||||
with_them do
|
||||
let_it_be(:project) { create(:project) }
|
||||
subject { Gitlab::Ci::Variables::Collection::Sorted.new(variables, project) }
|
||||
let(:collection) { Gitlab::Ci::Variables::Collection.new(variables) }
|
||||
|
||||
it 'sort returns correctly sorted variables' do
|
||||
expect(subject.sort.map { |var| var[:key] }).to eq(result)
|
||||
subject { Gitlab::Ci::Variables::Collection::Sorted.new(collection, project).sort }
|
||||
|
||||
it 'returns correctly sorted variables' do
|
||||
expect(subject.map { |var| var[:key] }).to eq(result)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -160,4 +160,24 @@ RSpec.describe Gitlab::UsageDataCounters::QuickActionActivityUniqueCounter, :cle
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'tracking invite_email' do
|
||||
let(:quickaction_name) { 'invite_email' }
|
||||
|
||||
context 'single email' do
|
||||
let(:args) { 'someone@gitlab.com' }
|
||||
|
||||
it_behaves_like 'a tracked quick action unique event' do
|
||||
let(:action) { 'i_quickactions_invite_email_single' }
|
||||
end
|
||||
end
|
||||
|
||||
context 'multiple emails' do
|
||||
let(:args) { 'someone@gitlab.com another@gitlab.com' }
|
||||
|
||||
it_behaves_like 'a tracked quick action unique event' do
|
||||
let(:action) { 'i_quickactions_invite_email_multiple' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -377,7 +377,7 @@ RSpec.describe ApplicationSetting do
|
|||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'an object with email-formated attributes', :abuse_notification_email do
|
||||
it_behaves_like 'an object with email-formatted attributes', :abuse_notification_email do
|
||||
subject { setting }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ RSpec.describe BulkImports::Entity, type: :model do
|
|||
it { is_expected.to validate_presence_of(:source_type) }
|
||||
it { is_expected.to validate_presence_of(:source_full_path) }
|
||||
it { is_expected.to validate_presence_of(:destination_name) }
|
||||
it { is_expected.to validate_presence_of(:destination_namespace) }
|
||||
|
||||
it { is_expected.to define_enum_for(:source_type).with_values(%i[group_entity project_entity]) }
|
||||
|
||||
|
|
@ -38,7 +37,11 @@ RSpec.describe BulkImports::Entity, type: :model do
|
|||
context 'when associated with a group and no project' do
|
||||
it 'is valid as a group_entity' do
|
||||
entity = build(:bulk_import_entity, :group_entity, group: build(:group), project: nil)
|
||||
expect(entity).to be_valid
|
||||
end
|
||||
|
||||
it 'is valid when destination_namespace is empty' do
|
||||
entity = build(:bulk_import_entity, :group_entity, group: build(:group), project: nil, destination_namespace: '')
|
||||
expect(entity).to be_valid
|
||||
end
|
||||
|
||||
|
|
@ -57,6 +60,12 @@ RSpec.describe BulkImports::Entity, type: :model do
|
|||
expect(entity).to be_valid
|
||||
end
|
||||
|
||||
it 'is invalid when destination_namespace is nil' do
|
||||
entity = build(:bulk_import_entity, :group_entity, group: build(:group), project: nil, destination_namespace: nil)
|
||||
expect(entity).not_to be_valid
|
||||
expect(entity.errors).to include(:destination_namespace)
|
||||
end
|
||||
|
||||
it 'is invalid as a project_entity' do
|
||||
entity = build(:bulk_import_entity, :group_entity, group: nil, project: build(:project))
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ RSpec.describe Email do
|
|||
end
|
||||
|
||||
describe 'validations' do
|
||||
it_behaves_like 'an object with RFC3696 compliant email-formated attributes', :email do
|
||||
it_behaves_like 'an object with RFC3696 compliant email-formatted attributes', :email do
|
||||
subject { build(:email) }
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -11,9 +11,14 @@ RSpec.describe IssueEmailParticipant do
|
|||
subject { build(:issue_email_participant) }
|
||||
|
||||
it { is_expected.to validate_presence_of(:issue) }
|
||||
it { is_expected.to validate_presence_of(:email) }
|
||||
it { is_expected.to validate_uniqueness_of(:email).scoped_to([:issue_id]) }
|
||||
it { is_expected.to validate_uniqueness_of(:email).scoped_to([:issue_id]).ignoring_case_sensitivity }
|
||||
|
||||
it_behaves_like 'an object with RFC3696 compliant email-formated attributes', :email
|
||||
it_behaves_like 'an object with RFC3696 compliant email-formatted attributes', :email
|
||||
|
||||
it 'is invalid if the email is nil' do
|
||||
subject.email = nil
|
||||
|
||||
expect(subject).to be_invalid
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1258,4 +1258,12 @@ RSpec.describe Issue do
|
|||
expect { issue.issue_type_supports?(:unkown_feature) }.to raise_error(ArgumentError)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#email_participants_downcase' do
|
||||
it 'returns a list of emails with all uppercase letters replaced with their lowercase counterparts' do
|
||||
participant = create(:issue_email_participant, email: 'SomEoNe@ExamPLe.com')
|
||||
|
||||
expect(participant.issue.email_participants_downcase).to match([participant.email.downcase])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ RSpec.describe Member do
|
|||
it { is_expected.to allow_value(nil).for(:expires_at) }
|
||||
end
|
||||
|
||||
it_behaves_like 'an object with email-formated attributes', :invite_email do
|
||||
it_behaves_like 'an object with email-formatted attributes', :invite_email do
|
||||
subject { build(:project_member) }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -381,11 +381,11 @@ RSpec.describe User do
|
|||
it { is_expected.not_to allow_value(-1).for(:projects_limit) }
|
||||
it { is_expected.not_to allow_value(Gitlab::Database::MAX_INT_VALUE + 1).for(:projects_limit) }
|
||||
|
||||
it_behaves_like 'an object with email-formated attributes', :email do
|
||||
it_behaves_like 'an object with email-formatted attributes', :email do
|
||||
subject { build(:user) }
|
||||
end
|
||||
|
||||
it_behaves_like 'an object with RFC3696 compliant email-formated attributes', :public_email, :notification_email do
|
||||
it_behaves_like 'an object with RFC3696 compliant email-formatted attributes', :public_email, :notification_email do
|
||||
subject { create(:user).tap { |user| user.emails << build(:email, email: email_value, confirmed_at: Time.current) } }
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateNamespaceService, '#execute'
|
|||
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
|
||||
stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace)
|
||||
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace)
|
||||
stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME, namespace: namespace)
|
||||
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME, namespace: namespace)
|
||||
|
||||
stub_kubeclient_get_secret(
|
||||
api_url,
|
||||
|
|
|
|||
|
|
@ -147,6 +147,8 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
|
|||
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_KNATIVE_SERVING_ROLE_BINDING_NAME, namespace: namespace)
|
||||
stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_NAME, namespace: namespace)
|
||||
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CROSSPLANE_DATABASE_ROLE_BINDING_NAME, namespace: namespace)
|
||||
stub_kubeclient_put_role(api_url, Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME, namespace: namespace)
|
||||
stub_kubeclient_put_role_binding(api_url, Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME, namespace: namespace)
|
||||
end
|
||||
|
||||
it 'creates a namespace object' do
|
||||
|
|
@ -243,6 +245,47 @@ RSpec.describe Clusters::Kubernetes::CreateOrUpdateServiceAccountService do
|
|||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'creates a role granting cilium permissions to the service account' do
|
||||
subject
|
||||
|
||||
expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/roles/#{Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME}").with(
|
||||
body: hash_including(
|
||||
metadata: {
|
||||
name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME,
|
||||
namespace: namespace
|
||||
},
|
||||
rules: [{
|
||||
apiGroups: %w(cilium.io),
|
||||
resources: %w(ciliumnetworkpolicies),
|
||||
verbs: %w(get list create update patch)
|
||||
}]
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
it 'creates a role binding granting cilium permissions to the service account' do
|
||||
subject
|
||||
|
||||
expect(WebMock).to have_requested(:put, api_url + "/apis/rbac.authorization.k8s.io/v1/namespaces/#{namespace}/rolebindings/#{Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME}").with(
|
||||
body: hash_including(
|
||||
metadata: {
|
||||
name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_BINDING_NAME,
|
||||
namespace: namespace
|
||||
},
|
||||
roleRef: {
|
||||
apiGroup: 'rbac.authorization.k8s.io',
|
||||
kind: 'Role',
|
||||
name: Clusters::Kubernetes::GITLAB_CILIUM_ROLE_NAME
|
||||
},
|
||||
subjects: [{
|
||||
kind: 'ServiceAccount',
|
||||
name: service_account_name,
|
||||
namespace: namespace
|
||||
}]
|
||||
)
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -1949,6 +1949,100 @@ RSpec.describe QuickActions::InterpretService do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'invite_email command' do
|
||||
let_it_be(:issuable) { issue }
|
||||
|
||||
it_behaves_like 'empty command', "No email participants were added. Either none were provided, or they already exist." do
|
||||
let(:content) { '/invite_email' }
|
||||
end
|
||||
|
||||
context 'with existing email participant' do
|
||||
let(:content) { '/invite_email a@gitlab.com' }
|
||||
|
||||
before do
|
||||
issuable.issue_email_participants.create!(email: "a@gitlab.com")
|
||||
end
|
||||
|
||||
it_behaves_like 'empty command', "No email participants were added. Either none were provided, or they already exist."
|
||||
end
|
||||
|
||||
context 'with new email participants' do
|
||||
let(:content) { '/invite_email a@gitlab.com b@gitlab.com' }
|
||||
|
||||
subject(:add_emails) { service.execute(content, issuable) }
|
||||
|
||||
it 'returns message' do
|
||||
_, _, message = add_emails
|
||||
|
||||
expect(message).to eq('Added a@gitlab.com and b@gitlab.com.')
|
||||
end
|
||||
|
||||
it 'adds 2 participants' do
|
||||
expect { add_emails }.to change { issue.issue_email_participants.count }.by(2)
|
||||
end
|
||||
|
||||
context 'with mixed case email' do
|
||||
let(:content) { '/invite_email FirstLast@GitLab.com' }
|
||||
|
||||
it 'returns correctly cased message' do
|
||||
_, _, message = add_emails
|
||||
|
||||
expect(message).to eq('Added FirstLast@GitLab.com.')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with invalid email' do
|
||||
let(:content) { '/invite_email a@gitlab.com bad_email' }
|
||||
|
||||
it 'only adds valid emails' do
|
||||
expect { add_emails }.to change { issue.issue_email_participants.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with existing email' do
|
||||
let(:content) { '/invite_email a@gitlab.com existing@gitlab.com' }
|
||||
|
||||
it 'only adds new emails' do
|
||||
issue.issue_email_participants.create!(email: 'existing@gitlab.com')
|
||||
|
||||
expect { add_emails }.to change { issue.issue_email_participants.count }.by(1)
|
||||
end
|
||||
|
||||
it 'only adds new (case insensitive) emails' do
|
||||
issue.issue_email_participants.create!(email: 'EXISTING@gitlab.com')
|
||||
|
||||
expect { add_emails }.to change { issue.issue_email_participants.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with duplicate email' do
|
||||
let(:content) { '/invite_email a@gitlab.com a@gitlab.com' }
|
||||
|
||||
it 'only adds unique new emails' do
|
||||
expect { add_emails }.to change { issue.issue_email_participants.count }.by(1)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with more than 6 emails' do
|
||||
let(:content) { '/invite_email a@gitlab.com b@gitlab.com c@gitlab.com d@gitlab.com e@gitlab.com f@gitlab.com g@gitlab.com' }
|
||||
|
||||
it 'only adds 6 new emails' do
|
||||
expect { add_emails }.to change { issue.issue_email_participants.count }.by(6)
|
||||
end
|
||||
end
|
||||
|
||||
context 'with feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(issue_email_participants: false)
|
||||
end
|
||||
|
||||
it 'does not add any participants' do
|
||||
expect { add_emails }.not_to change { issue.issue_email_participants.count }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#explain' do
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
# Note: You have access to `email_value` which is the email address value
|
||||
# being currently tested).
|
||||
|
||||
RSpec.shared_examples 'an object with email-formated attributes' do |*attributes|
|
||||
RSpec.shared_examples 'an object with email-formatted attributes' do |*attributes|
|
||||
attributes.each do |attribute|
|
||||
describe "specifically its :#{attribute} attribute" do
|
||||
%w[
|
||||
|
|
@ -45,7 +45,7 @@ RSpec.shared_examples 'an object with email-formated attributes' do |*attributes
|
|||
end
|
||||
end
|
||||
|
||||
RSpec.shared_examples 'an object with RFC3696 compliant email-formated attributes' do |*attributes|
|
||||
RSpec.shared_examples 'an object with RFC3696 compliant email-formatted attributes' do |*attributes|
|
||||
attributes.each do |attribute|
|
||||
describe "specifically its :#{attribute} attribute" do
|
||||
%w[
|
||||
|
|
|
|||
Loading…
Reference in New Issue