Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-02-22 12:10:38 +00:00
parent 2e9f877e8b
commit 980d813e90
65 changed files with 770 additions and 81 deletions

View File

@ -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

View File

@ -1 +1 @@
3f29772a7241e66cc89608778b2411f2a3733a17
c45afa70f5bd9723f0836ff228a11bc896c45511

View File

@ -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>

View File

@ -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)"

View File

@ -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,
},
],
};
},
},

View File

@ -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),
},
});
},

View File

@ -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;

View File

@ -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'

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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}"

View File

@ -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 } }

View File

@ -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),

View File

@ -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

View File

@ -0,0 +1,5 @@
---
title: Add invite_email Quick Action
merge_request: 49264
author: Lee Tickett
type: added

View File

@ -0,0 +1,5 @@
---
title: Add GlToggle label in edit feature flag
merge_request: 54546
author: Yogi (@yo)
type: changed

View File

@ -0,0 +1,5 @@
---
title: Render version dropdowns in MR changes view above tab navbar
merge_request: 54159
author: Simon Stieger @sim0
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Fix rendering of projects when the last pipeline changes during rendering
merge_request: 54651
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Add Role and Rolebinding for CiliumNetworkPolicies
merge_request: 54130
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Skip two factor setup for help pages
merge_request: 54739
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Update LaTeX Docker image in CI Templates to TexLive 2020
merge_request: 52043
author: Michael Schmitt @schmitmd
type: changed

View File

@ -0,0 +1,5 @@
---
title: Allow importing groups as new top-level groups
merge_request: 54323
author:
type: changed

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
a7397b8f5d00b85f8c963f04ae062393b21313d97c9b9875a88a76608b98f826

View File

@ -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);

View File

@ -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 |

View File

@ -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. | |

View File

@ -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 |
| --- | --- |

View File

@ -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. |

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',
});
});
});
});

View File

@ -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);

View File

@ -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({

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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))

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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[