Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-07-28 15:09:57 +00:00
parent b1e352740b
commit 1d9f78b3a4
116 changed files with 1870 additions and 468 deletions

View File

@ -0,0 +1,9 @@
## Scope
This issue is part of a bigger development effort described in detail by its epic. The scope of this issue is to ...
## Actions
<!-- Likely in the form of checkboxed elements -->
- [ ] TODO

View File

@ -2459,17 +2459,6 @@ Gitlab/FeatureAvailableUsage:
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/327490
Style/RegexpLiteralMixedPreserve:
Exclude:
- 'ee/app/models/status_page/project_setting.rb'
- 'ee/app/presenters/vulnerability_presenter.rb'
- 'ee/lib/api/geo_nodes.rb'
- 'ee/lib/gitlab/vulnerabilities/standard_vulnerability.rb'
- 'lib/api/invitations.rb'
- 'lib/gitlab/ci/pipeline/expression/lexeme/pattern.rb'
- 'lib/gitlab/metrics/requests_rack_middleware.rb'
- 'lib/gitlab/metrics/subscribers/active_record.rb'
- 'lib/gitlab/regex.rb'
- 'lib/gitlab/utils.rb'
- 'lib/product_analytics/tracker.rb'
- 'qa/qa/page/project/settings/advanced.rb'
- 'qa/spec/service/docker_run/gitlab_runner_spec.rb'
- 'rubocop/cop/gitlab/duplicate_spec_location.rb'

View File

@ -506,7 +506,10 @@ export default {
);
}
if (window.gon?.features?.diffsVirtualScrolling) {
if (
window.gon?.features?.diffsVirtualScrolling ||
window.gon?.features?.diffSearchingUsageData
) {
let keydownTime;
Mousetrap.bind(['mod+f', 'mod+g'], () => {
keydownTime = new Date().getTime();
@ -520,6 +523,11 @@ export default {
// and max 1000ms to be sure it the search box is filtered
if (delta >= 0 && delta < 1000) {
this.disableVirtualScroller = true;
if (window.gon?.features?.diffSearchingUsageData) {
api.trackRedisHllUserEvent('i_code_review_user_searches_diff');
api.trackRedisCounterEvent('user_searches_diffs');
}
}
}
});

View File

@ -1,3 +1,3 @@
import { mount2faAuthentication } from '~/authentication/mount_2fa';
document.addEventListener('DOMContentLoaded', mount2faAuthentication);
mount2faAuthentication();

View File

@ -1,3 +1,3 @@
import initEnvironmentsFolderBundle from '~/environments/folder/environments_folder_bundle';
document.addEventListener('DOMContentLoaded', initEnvironmentsFolderBundle);
initEnvironmentsFolderBundle();

View File

@ -1,3 +1,3 @@
import monitoringApp from '~/monitoring/monitoring_app';
document.addEventListener('DOMContentLoaded', monitoringApp);
monitoringApp();

View File

@ -1,3 +1,3 @@
import initTerminal from '~/terminal/';
document.addEventListener('DOMContentLoaded', initTerminal);
initTerminal();

View File

@ -2,7 +2,7 @@ import $ from 'jquery';
import ShortcutsNetwork from '~/behaviors/shortcuts/shortcuts_network';
import Network from '../network';
document.addEventListener('DOMContentLoaded', () => {
(() => {
if (!$('.network-graph').length) return;
const networkGraph = new Network({
@ -14,4 +14,4 @@ document.addEventListener('DOMContentLoaded', () => {
// eslint-disable-next-line no-new
new ShortcutsNetwork(networkGraph.branch_graph);
});
})();

View File

@ -3,7 +3,6 @@ import { GlIcon, GlSprintf, GlLink, GlFormCheckbox, GlToggle } from '@gitlab/ui'
import settingsMixin from 'ee_else_ce/pages/projects/shared/permissions/mixins/settings_pannel_mixin';
import { s__ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
visibilityOptions,
visibilityLevelDescriptions,
@ -48,7 +47,7 @@ export default {
GlFormCheckbox,
GlToggle,
},
mixins: [settingsMixin, glFeatureFlagsMixin()],
mixins: [settingsMixin],
props: {
requestCveAvailable: {
@ -737,22 +736,5 @@ export default {
}}</template>
</gl-form-checkbox>
</project-setting-row>
<project-setting-row
v-if="glFeatures.allowEditingCommitMessages"
ref="allow-editing-commit-messages"
class="gl-mb-4"
>
<input
:value="allowEditingCommitMessages"
type="hidden"
name="project[project_setting_attributes][allow_editing_commit_messages]"
/>
<gl-form-checkbox v-model="allowEditingCommitMessages">
{{ s__('ProjectSettings|Allow editing commit messages') }}
<template #help>{{
s__('ProjectSettings|Commit authors can edit commit messages on unprotected branches.')
}}</template>
</gl-form-checkbox>
</project-setting-row>
</div>
</template>

View File

@ -1,5 +1,3 @@
import initStaticSiteEditor from '~/static_site_editor';
window.addEventListener('DOMContentLoaded', () => {
initStaticSiteEditor(document.querySelector('#static-site-editor'));
});
initStaticSiteEditor(document.querySelector('#static-site-editor'));

View File

@ -1,5 +1,5 @@
<script>
import { GlModal, GlSprintf } from '@gitlab/ui';
import { GlModal, GlSprintf, GlFormInput } from '@gitlab/ui';
import { n__ } from '~/locale';
import {
REMOVE_TAG_CONFIRMATION_TEXT,
@ -12,6 +12,7 @@ export default {
components: {
GlModal,
GlSprintf,
GlFormInput,
},
props: {
itemsToBeDeleted: {
@ -25,7 +26,15 @@ export default {
required: false,
},
},
data() {
return {
projectPath: '',
};
},
computed: {
imageProjectPath() {
return this.itemsToBeDeleted[0]?.project?.path;
},
modalTitle() {
if (this.deleteImage) {
return DELETE_IMAGE_CONFIRMATION_TITLE;
@ -40,6 +49,7 @@ export default {
if (this.deleteImage) {
return {
message: DELETE_IMAGE_CONFIRMATION_TEXT,
item: this.imageProjectPath,
};
}
if (this.itemsToBeDeleted.length > 1) {
@ -55,6 +65,9 @@ export default {
item: first?.path,
};
},
disablePrimaryButton() {
return this.deleteImage && this.projectPath !== this.imageProjectPath;
},
},
methods: {
show() {
@ -69,10 +82,14 @@ export default {
ref="deleteModal"
modal-id="delete-tag-modal"
ok-variant="danger"
:action-primary="{ text: __('Confirm'), attributes: { variant: 'danger' } }"
:action-primary="{
text: __('Delete'),
attributes: [{ variant: 'danger' }, { disabled: disablePrimaryButton }],
}"
:action-cancel="{ text: __('Cancel') }"
@primary="$emit('confirmDelete')"
@cancel="$emit('cancelDelete')"
@change="projectPath = ''"
>
<template #modal-title>{{ modalTitle }}</template>
<p v-if="modalDescription" data-testid="description">
@ -80,7 +97,13 @@ export default {
<template #item>
<b>{{ modalDescription.item }}</b>
</template>
<template #code>
<code>{{ modalDescription.item }}</code>
</template>
</gl-sprintf>
</p>
<div v-if="deleteImage">
<gl-form-input v-model="projectPath" />
</div>
</gl-modal>
</template>

View File

@ -1,5 +1,5 @@
<script>
import { GlButton, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { GlIcon, GlTooltipDirective, GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { sprintf, n__, s__ } from '~/locale';
import MetadataItem from '~/vue_shared/components/registry/metadata_item.vue';
import TitleArea from '~/vue_shared/components/registry/title_area.vue';
@ -27,7 +27,7 @@ import getContainerRepositoryTagsCountQuery from '../../graphql/queries/get_cont
export default {
name: 'DetailsHeader',
components: { GlButton, GlIcon, TitleArea, MetadataItem },
components: { GlIcon, TitleArea, MetadataItem, GlDropdown, GlDropdownItem },
directives: {
GlTooltip: GlTooltipDirective,
},
@ -143,9 +143,22 @@ export default {
/>
</template>
<template #right-actions>
<gl-button variant="danger" :disabled="deleteButtonDisabled" @click="$emit('delete')">
{{ __('Delete image repository') }}
</gl-button>
<gl-dropdown
icon="ellipsis_v"
text="More actions"
:text-sr-only="true"
category="tertiary"
no-caret
right
>
<gl-dropdown-item
variant="danger"
:disabled="deleteButtonDisabled"
@click="$emit('delete')"
>
{{ __('Delete image repository') }}
</gl-dropdown-item>
</gl-dropdown>
</template>
</title-area>
</template>

View File

@ -99,7 +99,7 @@ export const DETAILS_DELETE_IMAGE_ERROR_MESSAGE = s__(
export const DELETE_IMAGE_CONFIRMATION_TITLE = s__('ContainerRegistry|Delete image repository?');
export const DELETE_IMAGE_CONFIRMATION_TEXT = s__(
'ContainerRegistry|Deleting the image repository will delete all images and tags inside. This action cannot be undone.',
'ContainerRegistry|Deleting the image repository will delete all images and tags inside. This action cannot be undone. Please type the following to confirm: %{code}',
);
export const SCHEDULED_FOR_DELETION_STATUS_TITLE = s__(

View File

@ -12,6 +12,7 @@ query getContainerRepositoryDetails($id: ID!) {
expirationPolicyCleanupStatus
project {
visibility
path
containerExpirationPolicy {
enabled
nextRunAt

View File

@ -161,7 +161,7 @@ export default {
},
deleteImage() {
this.deleteImageAlert = true;
this.itemsToBeDeleted = [{ path: this.containerRepository.path }];
this.itemsToBeDeleted = [{ ...this.containerRepository }];
this.$refs.deleteModal.show();
},
deleteImageError() {

View File

@ -1,6 +1,3 @@
import initSourcegraph from './index';
/**
* Load sourcegraph in it's own listener so that it's isolated from failures.
*/
document.addEventListener('DOMContentLoaded', initSourcegraph);
initSourcegraph();

View File

@ -0,0 +1,96 @@
# frozen_string_literal: true
module Analytics
module CycleAnalytics
module StageActions
include Gitlab::Utils::StrongMemoize
extend ActiveSupport::Concern
included do
include CycleAnalyticsParams
before_action :validate_params, only: %i[median]
end
def index
result = list_service.execute
if result.success?
render json: cycle_analytics_configuration(result.payload[:stages])
else
render json: { message: result.message }, status: result.http_status
end
end
def median
render json: { value: data_collector.median.seconds }
end
def average
render json: { value: data_collector.average.seconds }
end
def records
serialized_records = data_collector.serialized_records do |relation|
add_pagination_headers(relation)
end
render json: serialized_records
end
def count
render json: { count: data_collector.count }
end
private
def parent
raise NotImplementedError
end
def value_stream_class
raise NotImplementedError
end
def add_pagination_headers(relation)
Gitlab::Pagination::OffsetHeaderBuilder.new(
request_context: self,
per_page: relation.limit_value,
page: relation.current_page,
next_page: relation.next_page,
prev_page: relation.prev_page,
params: permitted_cycle_analytics_params
).execute(exclude_total_headers: true, data_without_counts: true)
end
def stage
@stage ||= ::Analytics::CycleAnalytics::StageFinder.new(parent: parent, stage_id: params[:id]).execute
end
def data_collector
@data_collector ||= Gitlab::Analytics::CycleAnalytics::DataCollector.new(
stage: stage,
params: request_params.to_data_collector_params
)
end
def value_stream
@value_stream ||= value_stream_class.build_default_value_stream(parent)
end
def list_params
{ value_stream: value_stream }
end
def list_service
Analytics::CycleAnalytics::Stages::ListService.new(parent: parent, current_user: current_user, params: list_params)
end
def cycle_analytics_configuration(stages)
stage_presenters = stages.map { |s| ::Analytics::CycleAnalytics::StagePresenter.new(s) }
Analytics::CycleAnalytics::ConfigurationEntity.new(stages: stage_presenters)
end
end
end
end

View File

@ -16,9 +16,20 @@ module CycleAnalyticsParams
end
def options(params)
@options ||= { from: start_date(params), current_user: current_user }.merge(date_range(params))
@options ||= {}.tap do |opts|
opts[:current_user] = current_user
opts[:projects] = params[:project_ids] if params[:project_ids]
opts[:group] = params[:group_id] if params[:group_id]
opts[:from] = params[:from] || start_date(params)
opts[:to] = params[:to] if params[:to]
opts[:end_event_filter] = params[:end_event_filter] if params[:end_event_filter]
opts.merge!(params.slice(*::Gitlab::Analytics::CycleAnalytics::RequestParams::FINDER_PARAM_NAMES))
opts.merge!(date_range(params))
end
end
private
def start_date(params)
case params[:start_date]
when '7'
@ -41,6 +52,27 @@ module CycleAnalyticsParams
date = field.is_a?(Date) || field.is_a?(Time) ? field : Date.parse(field)
date.to_time.utc
end
def permitted_cycle_analytics_params
params.permit(*::Gitlab::Analytics::CycleAnalytics::RequestParams::STRONG_PARAMS_DEFINITION)
end
def all_cycle_analytics_params
permitted_cycle_analytics_params.merge(current_user: current_user)
end
def request_params
@request_params ||= ::Gitlab::Analytics::CycleAnalytics::RequestParams.new(all_cycle_analytics_params)
end
def validate_params
if request_params.invalid?
render(
json: { message: 'Invalid parameters', errors: request_params.errors },
status: :unprocessable_entity
)
end
end
end
CycleAnalyticsParams.prepend_mod_with('CycleAnalyticsParams')

View File

@ -40,7 +40,7 @@ class Groups::EmailCampaignsController < Groups::ApplicationController
project_pipelines_url(group.projects.first)
when :trial
'https://about.gitlab.com/free-trial/'
when :team
when :team, :team_short
group_group_members_url(group)
end
end

View File

@ -1,6 +1,9 @@
# frozen_string_literal: true
class Projects::Analytics::CycleAnalytics::StagesController < Projects::ApplicationController
include ::Analytics::CycleAnalytics::StageActions
extend ::Gitlab::Utils::Override
respond_to :json
feature_category :planning_analytics
@ -8,37 +11,19 @@ class Projects::Analytics::CycleAnalytics::StagesController < Projects::Applicat
before_action :authorize_read_cycle_analytics!
before_action :only_default_value_stream_is_allowed!
def index
result = list_service.execute
private
if result.success?
render json: cycle_analytics_configuration(result.payload[:stages])
else
render json: { message: result.message }, status: result.http_status
end
override :parent
def parent
@project
end
private
override :value_stream_class
def value_stream_class
Analytics::CycleAnalytics::ProjectValueStream
end
def only_default_value_stream_is_allowed!
render_404 if params[:value_stream_id] != Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME
end
def value_stream
Analytics::CycleAnalytics::ProjectValueStream.build_default_value_stream(@project)
end
def list_params
{ value_stream: value_stream }
end
def list_service
Analytics::CycleAnalytics::Stages::ListService.new(parent: @project, current_user: current_user, params: list_params)
end
def cycle_analytics_configuration(stages)
stage_presenters = stages.map { |s| ::Analytics::CycleAnalytics::StagePresenter.new(s) }
Analytics::CycleAnalytics::ConfigurationEntity.new(stages: stage_presenters)
end
end

View File

@ -57,8 +57,14 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
def diffs_metadata
diffs = @compare.diffs(diff_options)
options = additional_attributes.merge(
only_context_commits: show_only_context_commits?,
merge_ref_head_diff: render_merge_ref_head_diff?,
allow_tree_conflicts: display_merge_conflicts_in_diff?
)
render json: DiffsMetadataSerializer.new(project: @merge_request.project, current_user: current_user)
.represent(diffs, additional_attributes.merge(only_context_commits: show_only_context_commits?))
.represent(diffs, options)
end
private

View File

@ -44,6 +44,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
# Usage data feature flags
push_frontend_feature_flag(:users_expanding_widgets_usage_data, @project, default_enabled: :yaml)
push_frontend_feature_flag(:diff_settings_usage_data, default_enabled: :yaml)
push_frontend_feature_flag(:diff_searching_usage_data, @project, default_enabled: :yaml)
experiment(:invite_members_in_comment, namespace: @project.root_ancestor) do |experiment_instance|
experiment_instance.exclude! unless helpers.can_import_members?

View File

@ -31,10 +31,6 @@ class ProjectsController < Projects::ApplicationController
# Project Export Rate Limit
before_action :export_rate_limit, only: [:export, :download_export, :generate_new_export]
before_action only: [:edit] do
push_frontend_feature_flag(:allow_editing_commit_messages, @project)
end
before_action do
push_frontend_feature_flag(:refactor_blob_viewer, @project, default_enabled: :yaml)
push_frontend_feature_flag(:increase_page_size_exponentially, @project, default_enabled: :yaml)
@ -399,7 +395,6 @@ class ProjectsController < Projects::ApplicationController
%i[
show_default_award_emojis
squash_option
allow_editing_commit_messages
mr_default_target_self
]
end

View File

@ -5,6 +5,8 @@ class SearchController < ApplicationController
include SearchHelper
include RedisTracking
RESCUE_FROM_TIMEOUT_ACTIONS = [:count, :show].freeze
track_redis_hll_event :show, name: 'i_search_total'
around_action :allow_gitaly_ref_name_caching
@ -154,12 +156,21 @@ class SearchController < ApplicationController
end
def render_timeout(exception)
raise exception unless action_name.to_sym == :show
raise exception unless action_name.to_sym.in?(RESCUE_FROM_TIMEOUT_ACTIONS)
log_exception(exception)
@timeout = true
render status: :request_timeout
if count_action_name?
render json: {}, status: :request_timeout
else
render status: :request_timeout
end
end
def count_action_name?
action_name.to_sym == :count
end
end

View File

@ -31,6 +31,12 @@ module Mutations
def ready?(**args)
raise_resource_not_available_error! ERROR_MESSAGE if Gitlab::Database.read_only?
missing_args = self.class.arguments.values
.reject { |arg| arg.accepts?(args.fetch(arg.keyword, :not_given)) }
.map(&:graphql_name)
raise ArgumentError, "Arguments must be provided: #{missing_args.join(", ")}" if missing_args.any?
true
end

View File

@ -7,17 +7,8 @@ module Mutations
argument :due_date,
Types::TimeType,
required: false,
description: 'The desired due date for the issue, ' \
'due date will be removed if absent or set to null'
def ready?(**args)
unless args.key?(:due_date)
raise Gitlab::Graphql::Errors::ArgumentError, 'Argument dueDate must be provided (`null` accepted)'
end
super
end
required: :nullable,
description: 'The desired due date for the issue. Due date is removed if null.'
def resolve(project_path:, iid:, due_date:)
issue = authorized_find!(project_path: project_path, iid: iid)

View File

@ -10,7 +10,29 @@ module Types
@deprecation = gitlab_deprecation(kwargs)
@doc_reference = kwargs.delete(:see)
# our custom addition `nullable` which allows us to declare
# an argument that must be provided, even if its value is null.
# When `required: true` then required arguments must not be null.
@gl_required = !!kwargs[:required]
@gl_nullable = kwargs[:required] == :nullable
# Only valid if an argument is also required.
if @gl_nullable
# Since the framework asserts that "required" means "cannot be null"
# we have to switch off "required" but still do the check in `ready?` behind the scenes
kwargs[:required] = false
end
super(*args, **kwargs, &block)
end
def accepts?(value)
# if the argument is declared as required, it must be included
return false if @gl_required && value == :not_given
# if the argument is declared as required, the value can only be null IF it is also nullable.
return false if @gl_required && value.nil? && !@gl_nullable
true
end
end
end

View File

@ -491,7 +491,6 @@ module ProjectsHelper
def project_permissions_settings(project)
feature = project.project_feature
{
packagesEnabled: !!project.packages_enabled,
visibilityLevel: project.visibility_level,
@ -511,7 +510,6 @@ module ProjectsHelper
metricsDashboardAccessLevel: feature.metrics_dashboard_access_level,
operationsAccessLevel: feature.operations_access_level,
showDefaultAwardEmojis: project.show_default_award_emojis?,
allowEditingCommitMessages: project.allow_editing_commit_messages?,
securityAndComplianceAccessLevel: project.security_and_compliance_access_level
}
end

View File

@ -69,21 +69,26 @@ class WebHook < ApplicationRecord
end
def disable!
update!(recent_failures: FAILURE_THRESHOLD + 1)
update_attribute(:recent_failures, FAILURE_THRESHOLD + 1)
end
def enable!
return if recent_failures == 0 && disabled_until.nil? && backoff_count == 0
update!(recent_failures: 0, disabled_until: nil, backoff_count: 0)
assign_attributes(recent_failures: 0, disabled_until: nil, backoff_count: 0)
save(validate: false)
end
def backoff!
update!(disabled_until: next_backoff.from_now, backoff_count: backoff_count.succ.clamp(0, MAX_FAILURES))
assign_attributes(disabled_until: next_backoff.from_now, backoff_count: backoff_count.succ.clamp(0, MAX_FAILURES))
save(validate: false)
end
def failed!
update!(recent_failures: recent_failures + 1) if recent_failures < MAX_FAILURES
return unless recent_failures < MAX_FAILURES
assign_attributes(recent_failures: recent_failures + 1)
save(validate: false)
end
# Overridden in ProjectHook and GroupHook, other webhooks are not rate-limited.

View File

@ -435,7 +435,7 @@ class Project < ApplicationRecord
delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, to: :ci_cd_settings, allow_nil: true
delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true
delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?,
:allow_merge_on_skipped_pipeline=, :has_confluence?, :allow_editing_commit_messages?,
:allow_merge_on_skipped_pipeline=, :has_confluence?,
to: :project_setting
delegate :active?, to: :prometheus_integration, allow_nil: true, prefix: true

View File

@ -1,6 +1,10 @@
# frozen_string_literal: true
class ProjectSetting < ApplicationRecord
include IgnorableColumns
ignore_column :allow_editing_commit_messages, remove_with: '14.4', remove_after: '2021-09-10'
belongs_to :project, inverse_of: :project_setting
enum squash_option: {

View File

@ -19,7 +19,8 @@ module Users
verify: 1,
trial: 2,
team: 3,
experience: 4
experience: 4,
team_short: 5
}, _suffix: true
scope :without_track_and_series, -> (track, series) do

View File

@ -0,0 +1,24 @@
# frozen_string_literal: true
module DiffFileConflictType
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
included do
expose :conflict_type do |diff_file, options|
conflict_file = conflict_file(options, diff_file)
next unless conflict_file
conflict_file.conflict_type(diff_file)
end
end
private
def conflict_file(options, diff_file)
strong_memoize(:conflict_file) do
options[:conflicts] && options[:conflicts][diff_file.new_path]
end
end
end

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true
class DiffFileEntity < DiffFileBaseEntity
include DiffFileConflictType
include CommitsHelper
include IconsHelper
include Gitlab::Utils::StrongMemoize
@ -88,10 +89,4 @@ class DiffFileEntity < DiffFileBaseEntity
# If nothing is present, inline will be the default.
options.fetch(:diff_view, :inline).to_sym
end
def conflict_file(options, diff_file)
strong_memoize(:conflict_file) do
options[:conflicts] && options[:conflicts][diff_file.new_path]
end
end
end

View File

@ -1,6 +1,8 @@
# frozen_string_literal: true
class DiffFileMetadataEntity < Grape::Entity
include DiffFileConflictType
expose :added_lines
expose :removed_lines
expose :new_path

View File

@ -2,8 +2,13 @@
class DiffsMetadataEntity < DiffsEntity
unexpose :diff_files
expose :diff_files, using: DiffFileMetadataEntity do |diffs, _|
diffs.raw_diff_files(sorted: true)
expose :diff_files do |diffs, options|
DiffFileMetadataEntity.represent(
diffs.raw_diff_files(sorted: true),
options.merge(
conflicts: conflicts(allow_tree_conflicts: options[:allow_tree_conflicts])
)
)
end
expose :conflict_resolution_path do |_, options|

View File

@ -8,8 +8,13 @@ module Namespaces
completed_actions: [:created],
incomplete_actions: [:git_write]
},
team_short: {
interval_days: [1],
completed_actions: [:git_write],
incomplete_actions: [:user_added]
},
verify: {
interval_days: [1, 5, 10],
interval_days: [2, 6, 11],
completed_actions: [:git_write],
incomplete_actions: [:pipeline_created]
},
@ -98,13 +103,11 @@ module Namespaces
def can_perform_action?(user, group)
case track
when :create
user.can?(:create_projects, group)
when :verify
when :create, :verify
user.can?(:create_projects, group)
when :trial
user.can?(:start_trial, group)
when :team
when :team, :team_short
user.can?(:admin_group_member, group)
when :experience
true

View File

@ -1,5 +1,7 @@
%p.profile-settings-content
= s_("DeployTokens|Pick a name for your unique deploy token.")
- group_deploy_tokens_help_link_url = help_page_path('user/project/deploy_tokens/index.md')
- group_deploy_tokens_help_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: group_deploy_tokens_help_link_url }
= s_('DeployTokens|Create a new deploy token for all projects in this group. %{link_start}What are deploy tokens?%{link_end}').html_safe % { link_start: group_deploy_tokens_help_link_start, link_end: '</a>'.html_safe }
= form_for token, url: create_deploy_token_path(group_or_project, anchor: 'js-deploy-tokens'), method: :post, remote: Feature.enabled?(:ajax_new_deploy_token, group_or_project) do |f|
= form_errors(token)
@ -7,23 +9,26 @@
.form-group
= f.label :name, class: 'label-bold'
= f.text_field :name, class: 'form-control gl-form-input', data: { qa_selector: 'deploy_token_name_field' }, required: true
.text-secondary= s_('DeployTokens|Enter a unique name for your deploy token.')
.form-group
= f.label :expires_at, _('Expires at (optional)'), class: 'label-bold'
= f.label :expires_at, _('Expiration date (optional)'), class: 'label-bold'
= f.text_field :expires_at, class: 'datepicker form-control', data: { qa_selector: 'deploy_token_expires_at_field' }, value: f.object.expires_at
.text-secondary= s_('DeployTokens|Unless you enter a date, the token does not expire.')
.text-secondary= s_('DeployTokens|Enter an expiration date for your token. Defaults to never expire.')
.form-group
= f.label :username, _('Username (optional)'), class: 'label-bold'
= f.text_field :username, class: 'form-control'
.text-secondary= s_('DeployTokens|Unless you specify a username, it is set to "gitlab+deploy-token-{n}".')
.text-secondary
= html_escape(s_('DeployTokens|Enter a username for your token. Defaults to %{code_start}gitlab+deploy-token-{n}%{code_end}.')) % { code_start: '<code>'.html_safe, code_end: '</code>'.html_safe }
.form-group
= f.label :scopes, _('Scopes [Select 1 or more]'), class: 'label-bold'
= f.label :scopes, _('Scopes (select at least one)'), class: 'label-bold'
%fieldset.form-group.form-check
= f.check_box :read_repository, class: 'form-check-input', data: { qa_selector: 'deploy_token_read_repository_checkbox' }
= f.label :read_repository, 'read_repository', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read-only access to the repository.')
.text-secondary
= s_('DeployTokens|Allows read-only access to the repository.')
- if container_registry_enabled?(group_or_project)
%fieldset.form-group.form-check
@ -34,18 +39,18 @@
%fieldset.form-group.form-check
= f.check_box :write_registry, class: 'form-check-input'
= f.label :write_registry, 'write_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows write access to registry images.')
.text-secondary= s_('DeployTokens|Allows read and write access to registry images.')
- if packages_registry_enabled?(group_or_project)
%fieldset.form-group.form-check
= f.check_box :read_package_registry, class: 'form-check-input', data: { qa_selector: 'deploy_token_read_package_registry_checkbox' }
= f.label :read_package_registry, 'read_package_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows read access to the package registry.')
.text-secondary= s_('DeployTokens|Allows read-only access to the package registry.')
%fieldset.form-group.form-check
= f.check_box :write_package_registry, class: 'form-check-input'
= f.label :write_package_registry, 'write_package_registry', class: 'label-bold form-check-label'
.text-secondary= s_('DeployTokens|Allows write access to the package registry.')
.text-secondary= s_('DeployTokens|Allows read and write access to the package registry.')
.gl-mt-3
= f.submit s_('DeployTokens|Create deploy token'), class: 'btn gl-button btn-confirm', data: { qa_selector: 'create_deploy_token_button' }

View File

@ -11,7 +11,7 @@
- if @new_deploy_token.persisted?
= render 'shared/deploy_tokens/new_deploy_token', deploy_token: @new_deploy_token
%h5.gl-mt-0
= s_('DeployTokens|Add a deploy token')
= s_('DeployTokens|New deploy token')
= render 'shared/deploy_tokens/form', group_or_project: group_or_project, token: @new_deploy_token, presenter: @deploy_tokens
%hr
= render 'shared/deploy_tokens/table', group_or_project: group_or_project, active_tokens: @deploy_tokens

View File

@ -1,8 +0,0 @@
---
name: allow_editing_commit_messages
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49152/
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/290779
milestone: '13.7'
type: development
group:
default_enabled: false

View File

@ -0,0 +1,8 @@
---
name: diff_searching_usage_data
introduced_by_url:
rollout_issue_url:
milestone: '14.2'
type: development
group: group::code review
default_enabled: true

View File

@ -65,6 +65,7 @@
- 'i_code_review_diff_multiple_files'
- 'i_code_review_user_load_conflict_ui'
- 'i_code_review_user_resolve_conflict'
- 'i_code_review_user_searches_diff'
- name: code_review_category_monthly_active_users
operator: OR
source: redis
@ -122,6 +123,7 @@
- 'i_code_review_diff_multiple_files'
- 'i_code_review_user_load_conflict_ui'
- 'i_code_review_user_resolve_conflict'
- 'i_code_review_user_searches_diff'
- name: code_review_extension_category_monthly_active_users
operator: OR
source: redis

View File

@ -0,0 +1,21 @@
---
key_path: redis_hll_counters.code_review.i_code_review_user_searches_diff_monthly
description: Count of users who search merge request diffs
product_section: dev
product_stage: create
product_group: group::code review
product_category: code_review
value_type: number
status: implemented
milestone: '14.2'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66522
time_frame: 28d
data_source: redis_hll
data_category: Optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,21 @@
---
key_path: redis_hll_counters.code_review.i_code_review_user_searches_diff_weekly
description: Count of users who search merge request diffs
product_section: dev
product_stage: create
product_group: group::code review
product_category: code_review
value_type: number
status: implemented
milestone: '14.2'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66522
time_frame: 7d
data_source: redis_hll
data_category: Optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,21 @@
---
key_path: counts.diff_searches
description: Total count of merge request diff searches
product_section: dev
product_stage: create
product_group: group::code review
product_category: code_review
value_type: number
status: implemented
milestone: '14.2'
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66522
time_frame: all
data_source: redis
data_category: Optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,22 @@
---
key_path: counts.in_product_marketing_email_team_short_0_cta_clicked
name: "count_clicks_on_the_first_email_of_the_team_short_track_for_in_product_marketing_emails"
description: Total clicks on the team_short track's first email
product_section:
product_stage: growth
product_group: group::activation
product_category: onboarding
value_type: number
status: implemented
milestone: "14.2"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66854
time_frame: all
data_source: database
data_category: Optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -0,0 +1,22 @@
---
key_path: counts.in_product_marketing_email_team_short_0_sent
name: "count_sent_first_email_of_the_team_short_track_for_in_product_marketing_emails"
description: Total sent emails of the team_short track's first email
product_section:
product_stage: growth
product_group: group::activation
product_category: onboarding
value_type: number
status: implemented
milestone: "14.2"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66854
time_frame: all
data_source: database
data_category: Optional
distribution:
- ce
- ee
tier:
- free
- premium
- ultimate

View File

@ -283,7 +283,14 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resource :cycle_analytics, only: :show, path: 'value_stream_analytics'
scope module: :cycle_analytics, as: 'cycle_analytics', path: 'value_stream_analytics' do
resources :value_streams, only: [:index] do
resources :stages, only: [:index]
resources :stages, only: [:index] do
member do
get :median
get :average
get :records
get :count
end
end
end
resource :summary, controller: :summary, only: :show
end

View File

@ -6,14 +6,10 @@ class AddAllowToEditCommitToProjectSettings < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
with_lock_retries do
add_column :project_settings, :allow_editing_commit_messages, :boolean, default: false, null: false
end
# no-op
end
def down
with_lock_retries do
remove_column :project_settings, :allow_editing_commit_messages
end
# no-op
end
end

View File

@ -54,6 +54,16 @@ Returns [`CiConfig`](#ciconfig).
| <a id="queryciconfigprojectpath"></a>`projectPath` | [`ID!`](#id) | The project of the CI config. |
| <a id="queryciconfigsha"></a>`sha` | [`String`](#string) | Sha for the pipeline. |
### `Query.ciMinutesUsage`
The monthly CI minutes usage data for the current user.
Returns [`CiMinutesNamespaceMonthlyUsageConnection`](#ciminutesnamespacemonthlyusageconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
### `Query.containerRepository`
Find a container repository.
@ -2533,7 +2543,7 @@ Input type: `IssueSetDueDateInput`
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationissuesetduedateclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationissuesetduedateduedate"></a>`dueDate` | [`Time`](#time) | The desired due date for the issue, due date will be removed if absent or set to null. |
| <a id="mutationissuesetduedateduedate"></a>`dueDate` | [`Time`](#time) | The desired due date for the issue. Due date is removed if null. |
| <a id="mutationissuesetduedateiid"></a>`iid` | [`String!`](#string) | The IID of the issue to mutate. |
| <a id="mutationissuesetduedateprojectpath"></a>`projectPath` | [`ID!`](#id) | The project the issue to mutate is in. |
@ -4876,6 +4886,52 @@ The edge type for [`CiJob`](#cijob).
| <a id="cijobedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="cijobedgenode"></a>`node` | [`CiJob`](#cijob) | The item at the end of the edge. |
#### `CiMinutesNamespaceMonthlyUsageConnection`
The connection type for [`CiMinutesNamespaceMonthlyUsage`](#ciminutesnamespacemonthlyusage).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="ciminutesnamespacemonthlyusageconnectionedges"></a>`edges` | [`[CiMinutesNamespaceMonthlyUsageEdge]`](#ciminutesnamespacemonthlyusageedge) | A list of edges. |
| <a id="ciminutesnamespacemonthlyusageconnectionnodes"></a>`nodes` | [`[CiMinutesNamespaceMonthlyUsage]`](#ciminutesnamespacemonthlyusage) | A list of nodes. |
| <a id="ciminutesnamespacemonthlyusageconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `CiMinutesNamespaceMonthlyUsageEdge`
The edge type for [`CiMinutesNamespaceMonthlyUsage`](#ciminutesnamespacemonthlyusage).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="ciminutesnamespacemonthlyusageedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="ciminutesnamespacemonthlyusageedgenode"></a>`node` | [`CiMinutesNamespaceMonthlyUsage`](#ciminutesnamespacemonthlyusage) | The item at the end of the edge. |
#### `CiMinutesProjectMonthlyUsageConnection`
The connection type for [`CiMinutesProjectMonthlyUsage`](#ciminutesprojectmonthlyusage).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="ciminutesprojectmonthlyusageconnectionedges"></a>`edges` | [`[CiMinutesProjectMonthlyUsageEdge]`](#ciminutesprojectmonthlyusageedge) | A list of edges. |
| <a id="ciminutesprojectmonthlyusageconnectionnodes"></a>`nodes` | [`[CiMinutesProjectMonthlyUsage]`](#ciminutesprojectmonthlyusage) | A list of nodes. |
| <a id="ciminutesprojectmonthlyusageconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
#### `CiMinutesProjectMonthlyUsageEdge`
The edge type for [`CiMinutesProjectMonthlyUsage`](#ciminutesprojectmonthlyusage).
##### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="ciminutesprojectmonthlyusageedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
| <a id="ciminutesprojectmonthlyusageedgenode"></a>`node` | [`CiMinutesProjectMonthlyUsage`](#ciminutesprojectmonthlyusage) | The item at the end of the edge. |
#### `CiRunnerConnection`
The connection type for [`CiRunner`](#cirunner).
@ -7858,6 +7914,25 @@ Represents the total number of issues and their weights for a particular day.
| ---- | ---- | ----------- |
| <a id="cijobtokenscopetypeprojects"></a>`projects` | [`ProjectConnection!`](#projectconnection) | Allow list of projects that can be accessed by CI Job tokens created by this project. (see [Connections](#connections)) |
### `CiMinutesNamespaceMonthlyUsage`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="ciminutesnamespacemonthlyusageminutes"></a>`minutes` | [`Int`](#int) | The total number of minutes used by all projects in the namespace. |
| <a id="ciminutesnamespacemonthlyusagemonth"></a>`month` | [`String`](#string) | The month related to the usage data. |
| <a id="ciminutesnamespacemonthlyusageprojects"></a>`projects` | [`CiMinutesProjectMonthlyUsageConnection`](#ciminutesprojectmonthlyusageconnection) | CI minutes usage data for projects in the namespace. (see [Connections](#connections)) |
### `CiMinutesProjectMonthlyUsage`
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="ciminutesprojectmonthlyusageminutes"></a>`minutes` | [`Int`](#int) | The number of CI minutes used by the project in the month. |
| <a id="ciminutesprojectmonthlyusagename"></a>`name` | [`String`](#string) | The name of the project. |
### `CiRunner`
#### Fields

View File

@ -3277,7 +3277,7 @@ dashboards.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207528) in GitLab 13.0.
> - Requires [GitLab Runner](https://docs.gitlab.com/runner/) 11.5 and above.
The `terraform` report obtains a Terraform `tfplan.json` file. [JQ processing required to remove credentials](../../user/infrastructure/mr_integration.md#setup). The collected Terraform
The `terraform` report obtains a Terraform `tfplan.json` file. [JQ processing required to remove credentials](../../user/infrastructure/mr_integration.md#configure-terraform-report-artifacts). The collected Terraform
plan report uploads to GitLab as an artifact and displays
in merge requests. For more information, see
[Output `terraform plan` information into a merge request](../../user/infrastructure/mr_integration.md).

View File

@ -1366,6 +1366,31 @@ argument :my_arg, GraphQL::Types::String,
description: "A description of the argument."
```
#### Nullability
Arguments can be marked as `required: true` which means the value must be present and not `null`.
If a required argument's value can be `null`, use the `required: :nullable` declaration.
Example:
```ruby
argument :due_date,
Types::TimeType,
required: :nullable,
description: 'The desired due date for the issue. Due date is removed if null.'
```
In the above example, the `due_date` argument must be given, but unlike the GraphQL spec, the value can be `null`.
This allows 'unsetting' the due date in a single mutation rather than creating a new mutation for removing the due date.
```ruby
{ due_date: null } # => OK
{ due_date: "2025-01-10" } # => OK
{ } # => invalid (not given)
```
#### Keywords
Each GraphQL `argument` defined is passed to the `#resolve` method
of a mutation as keyword arguments.
@ -1377,6 +1402,8 @@ def resolve(my_arg:)
end
```
#### Input Types
`graphql-ruby` wraps up arguments into an
[input type](https://graphql.org/learn/schema/#input-types).

View File

@ -2638,6 +2638,34 @@ Status: `data_available`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_product_marketing_email_team_short_0_cta_clicked`
Total clicks on the team_short track's first email
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210727095918_in_product_marketing_email_team_short_0_cta_clicked.yml)
Group: `group::activation`
Data Category: `Optional`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_product_marketing_email_team_short_0_sent`
Total sent emails of the team_short track's first email
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210727095923_in_product_marketing_email_team_short_0_sent.yml)
Group: `group::activation`
Data Category: `Optional`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.in_product_marketing_email_trial_0_cta_clicked`
Total clicks on the verify trial's first email
@ -7650,6 +7678,20 @@ Status: `data_available`
Tiers: `free`, `premium`, `ultimate`
### `counts.user_searches_diffs`
Count of users who search merge request diffs
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_all/20210723075525_user_searches_diffs.yml)
Group: `group::code review`
Data Category: `Optional`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `counts.web_hooks`
Missing description
@ -10646,6 +10688,20 @@ Status: `data_available`
Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.code_review.i_code_review_searches_in_diff_monthly`
Count of searches in merge request diffs
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210722132444_i_code_review_searches_in_diff_monthly.yml)
Group: `group::code review`
Data Category: `Optional`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.code_review.i_code_review_user_add_suggestion_monthly`
Count of unique users per month who added a suggestion
@ -11514,6 +11570,20 @@ Status: `data_available`
Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.code_review.i_code_review_user_searches_diff_monthly`
Count of users who search merge request diffs
[YAML definition](https://gitlab.com/gitlab-org/gitlab/-/blob/master/config/metrics/counts_28d/20210720144005_i_code_review_user_searches_diff_monthly.yml)
Group: `group::code review`
Data Category: `Optional`
Status: `implemented`
Tiers: `free`, `premium`, `ultimate`
### `redis_hll_counters.code_review.i_code_review_user_single_file_diffs_monthly`
Count of unique users per month with diffs viewed file by file

View File

@ -0,0 +1,59 @@
---
stage: Create
group: Ecosystem
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Configure the Jira integration in GitLab
You can set up the [Jira integration](index.md#jira-integration)
by configuring your project settings in GitLab.
You can also configure these settings at a [group level](../../user/admin_area/settings/project_integration_management.md#manage-group-level-default-settings-for-a-project-integration),
and for self-managed GitLab, at an [instance level](../../user/admin_area/settings/project_integration_management.md#manage-instance-level-default-settings-for-a-project-integration).
Prerequisites:
- Ensure your GitLab installation does not use a [relative URL](development_panel.md#limitations).
- For **Jira Server**, ensure you have a Jira username and password.
See [authentication in Jira](index.md#authentication-in-jira).
- For **Jira on Atlassian cloud**, ensure you have an API token
and the email address you used to create the token.
See [authentication in Jira](index.md#authentication-in-jira).
To configure your project:
1. Go to your project and select [**Settings > Integrations**](../../user/project/integrations/overview.md#accessing-integrations).
1. Select **Jira**.
1. Select **Enable integration**.
1. Select **Trigger** actions. Your choice determines whether a mention of Jira issue
(in a GitLab commit, merge request, or both) creates a cross-link in Jira back to GitLab.
1. To comment in the Jira issue when a **Trigger** action is made in GitLab, select
**Enable comments**.
1. To transition Jira issues when a
[closing reference](../../user/project/issues/managing_issues.md#closing-issues-automatically)
is made in GitLab, select **Enable Jira transitions**.
1. Provide Jira configuration information:
- **Web URL**: The base URL to the Jira instance web interface you're linking to
this GitLab project, such as `https://jira.example.com`.
- **Jira API URL**: The base URL to the Jira instance API, such as `https://jira-api.example.com`.
Defaults to the **Web URL** value if not set. Leave blank if using **Jira on Atlassian cloud**.
- **Username or Email**:
For **Jira Server**, use `username`. For **Jira on Atlassian cloud**, use `email`.
- **Password/API token**:
Use `password` for **Jira Server** or `API token` for **Jira on Atlassian cloud**.
1. To enable users to view Jira issues inside the GitLab project **(PREMIUM)**, select **Enable Jira issues** and
enter a Jira project key.
You can display issues only from a single Jira project in a given GitLab project.
WARNING:
If you enable Jira issues with this setting, all users with access to this GitLab project
can view all issues from the specified Jira project.
1. To enable issue creation for vulnerabilities **(ULTIMATE)**, select **Enable Jira issues creation from vulnerabilities**.
1. Select the **Jira issue type**. If the dropdown is empty, select refresh (**{retry}**) and try again.
1. To verify the Jira connection is working, select **Test settings**.
1. Select **Save changes**.
Your GitLab project can now interact with all Jira projects in your instance and the project now
displays a Jira link that opens the Jira project.

View File

@ -68,8 +68,8 @@ To simplify administration, we recommend that a GitLab group maintainer or group
| Jira usage | GitLab.com customers need | GitLab self-managed customers need |
|------------|---------------------------|------------------------------------|
| [Atlassian cloud](https://www.atlassian.com/cloud) | The [GitLab.com for Jira Cloud](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) application installed from the [Atlassian Marketplace](https://marketplace.atlassian.com). This offers real-time sync between GitLab and Jira. | The [GitLab.com for Jira Cloud](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview), using a workaround process. See the documentation for [installing the GitLab Jira Cloud application for self-managed instances](connect-app.md#install-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances) for more information. |
| Your own server | The Jira DVCS (distributed version control system) connector. This syncs data hourly. | The [Jira DVCS Connector](dvcs.md). |
| [Atlassian cloud](https://www.atlassian.com/cloud) | The [GitLab.com for Jira Cloud](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview) application installed from the [Atlassian Marketplace](https://marketplace.atlassian.com). This offers real-time sync between GitLab and Jira. For more information, see the documentation for the [GitLab.com for Jira Cloud app](connect-app.md). | The [GitLab.com for Jira Cloud](https://marketplace.atlassian.com/apps/1221011/gitlab-com-for-jira-cloud?hosting=cloud&tab=overview), using a workaround process. See the documentation for [installing the GitLab Jira Cloud application for self-managed instances](connect-app.md#install-the-gitlabcom-for-jira-cloud-app-for-self-managed-instances) for more information. |
| Your own server | The [Jira DVCS (distributed version control system) connector](dvcs.md). This syncs data hourly. | The [Jira DVCS Connector](dvcs.md). |
Each GitLab project can be configured to connect to an entire Jira instance. That means after
configuration, one GitLab project can interact with all Jira projects in that instance. For:
@ -82,57 +82,6 @@ configuration, one GitLab project can interact with all Jira projects in that in
If you have a single Jira instance, you can pre-fill the settings. For more information, read the
documentation for [central administration of project integrations](../../user/admin_area/settings/project_integration_management.md).
To enable the integration in GitLab, you must:
1. [Configure the project in Jira](index.md#jira-integration).
The supported Jira versions are `v6.x`, `v7.x`, and `v8.x`.
1. [Enter the correct values in GitLab](#configure-gitlab).
### Configure GitLab
To enable the integration in your GitLab project, after you
[configure your Jira project](index.md#jira-integration):
1. Ensure your GitLab installation does not use a relative URL, as described in
[Limitations](#limitations).
1. Go to your project and select [**Settings > Integrations**](../../user/project/integrations/overview.md#accessing-integrations).
1. Select **Jira**.
1. Select **Enable integration**.
1. Select **Trigger** actions. Your choice determines whether a mention of Jira issue
(in a GitLab commit, merge request, or both) creates a cross-link in Jira back to GitLab.
1. To comment in the Jira issue when a **Trigger** action is made in GitLab, select
**Enable comments**.
1. To transition Jira issues when a
[closing reference](../../user/project/issues/managing_issues.md#closing-issues-automatically)
is made in GitLab, select **Enable Jira transitions**.
1. Provide Jira configuration information:
- **Web URL**: The base URL to the Jira instance web interface you're linking to
this GitLab project, such as `https://jira.example.com`.
- **Jira API URL**: The base URL to the Jira instance API, such as `https://jira-api.example.com`.
Defaults to the **Web URL** value if not set. Leave blank if using **Jira on Atlassian cloud**.
- **Username or Email**:
For **Jira Server**, use `username`. For **Jira on Atlassian cloud**, use `email`.
See [authentication in Jira](index.md#authentication-in-jira).
- **Password/API token**:
Use `password` for **Jira Server** or `API token` for **Jira on Atlassian cloud**.
See [authentication in Jira](index.md#authentication-in-jira).
1. To enable users to view Jira issues inside the GitLab project **(PREMIUM)**, select **Enable Jira issues** and
enter a Jira project key.
You can display issues only from a single Jira project in a given GitLab project.
WARNING:
If you enable Jira issues with this setting, all users with access to this GitLab project
can view all issues from the specified Jira project.
1. To enable issue creation for vulnerabilities **(ULTIMATE)**, select **Enable Jira issues creation from vulnerabilities**.
1. Select the **Jira issue type**. If the dropdown is empty, select refresh (**{retry}**) and try again.
1. To verify the Jira connection is working, select **Test settings**.
1. Select **Save changes**.
Your GitLab project can now interact with all Jira projects in your instance and the project now
displays a Jira link that opens the Jira project.
## Limitations
This integration is not supported on GitLab instances under a

View File

@ -26,7 +26,7 @@ The supported Jira versions are `v6.x`, `v7.x`, and `v8.x`.
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
For an overview, see [Agile Management - GitLab-Jira Basic Integration](https://www.youtube.com/watch?v=fWvwkx5_00E&feature=youtu.be).
To set up the integration, [configure the project settings](development_panel.md#configure-gitlab) in GitLab.
To set up the integration, [configure the project settings](configure.md) in GitLab.
You can also configure these settings at a [group level](../../user/admin_area/settings/project_integration_management.md#manage-group-level-default-settings-for-a-project-integration),
and for self-managed GitLab, at an [instance level](../../user/admin_area/settings/project_integration_management.md#manage-instance-level-default-settings-for-a-project-integration).

View File

@ -7,7 +7,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Jira integration issue management **(FREE)**
Integrating issue management with Jira requires you to [configure Jira](index.md#jira-integration)
and [enable the Jira service](development_panel.md#configure-gitlab) in GitLab.
and [enable the Jira integration](configure.md) in GitLab.
After you configure and enable the integration, you can reference and close Jira
issues by mentioning the Jira ID in GitLab commits and merge requests.
@ -102,7 +102,7 @@ Consider this example:
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3622) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
You can browse, search, and view issues from a selected Jira project directly in GitLab,
if your GitLab administrator [has configured it](development_panel.md#configure-gitlab).
if your GitLab administrator [has configured it](configure.md).
To do this, in GitLab, go to your project and select **Jira > Issues list**. The issue list
sorts by **Created date** by default, with the newest issues listed at the top:
@ -149,7 +149,7 @@ When you configure automatic issue transitions, you can transition a referenced
Jira issue to the next available status with a category of **Done**. To configure
this setting:
1. Refer to the [Configure GitLab](development_panel.md#configure-gitlab) instructions.
1. Refer to the [Configure GitLab](configure.md) instructions.
1. Select the **Enable Jira transitions** check box.
1. Select the **Move to Done** option.
@ -167,7 +167,7 @@ For advanced workflows, you can specify custom Jira transition IDs:
**action** parameter in the URL.
The transition ID may vary between workflows (for example, a bug instead of a
story), even if the status you're changing to is the same.
1. Refer to the [Configure GitLab](development_panel.md#configure-gitlab) instructions.
1. Refer to the [Configure GitLab](configure.md) instructions.
1. Select the **Enable Jira transitions** setting.
1. Select the **Custom transitions** option.
1. Enter your transition IDs in the text field. If you insert multiple transition IDs
@ -179,7 +179,7 @@ For advanced workflows, you can specify custom Jira transition IDs:
GitLab can cross-link source commits or merge requests with Jira issues without
adding a comment to the Jira issue:
1. Refer to the [Configure GitLab](development_panel.md#configure-gitlab) instructions.
1. Refer to the [Configure GitLab](configure.md) instructions.
1. Clear the **Enable comments** check box.
## Enable or disable the ability to require an associated Jira issue on merge requests

View File

@ -15,8 +15,8 @@ on Atlassian cloud. To create the API token:
1. Select **Create API token** to display a modal window with an API token.
1. To copy the API token, select **Copy to clipboard**, or select **View** and write
down the new API token. You need this value when you
[configure GitLab](development_panel.md#configure-gitlab).
[configure GitLab](configure.md).
You need the newly created token, and the email
address you used when you created it, when you
[configure GitLab](development_panel.md#configure-gitlab).
[configure GitLab](configure.md).

View File

@ -80,4 +80,4 @@ After creating the group in Jira, grant permissions to the group by creating a p
Write down the new Jira username and its
password, as you need them when
[configuring GitLab](development_panel.md#configure-gitlab).
[configuring GitLab](configure.md).

View File

@ -67,12 +67,6 @@ Amazon S3 or Google Cloud Storage. Its features include:
Read more on setting up and [using GitLab Managed Terraform states](../terraform_state.md)
WARNING:
Like any other job artifact, Terraform plan data is [viewable by anyone with Guest access](../../permissions.md) to the repository.
Neither Terraform nor GitLab encrypts the plan file by default. If your Terraform plan
includes sensitive data such as passwords, access tokens, or certificates, GitLab strongly
recommends encrypting plan output or modifying the project visibility settings.
## Terraform module registry
GitLab can be used as a [Terraform module registry](../../packages/terraform_module_registry/index.md)

View File

@ -15,12 +15,17 @@ you can expose details from `terraform plan` runs directly into a merge request
enabling you to see statistics about the resources that Terraform creates,
modifies, or destroys.
## Setup
WARNING:
Like any other job artifact, Terraform Plan data is [viewable by anyone with Guest access](../permissions.md) to the repository.
Neither Terraform nor GitLab encrypts the plan file by default. If your Terraform Plan
includes sensitive data such as passwords, access tokens, or certificates, we strongly
recommend encrypting plan output or modifying the project visibility settings.
## Configure Terraform report artifacts
NOTE:
GitLab ships with a [pre-built CI template](iac/index.md#quick-start) that uses GitLab Managed Terraform state and integrates Terraform changes into merge requests. We recommend customizing the pre-built image and relying on the `gitlab-terraform` helper provided within for a quick setup.
To manually configure a GitLab Terraform Report artifact requires the following steps:
To manually configure a GitLab Terraform Report artifact:
1. For simplicity, let's define a few reusable variables to allow us to
refer to these files multiple times:

View File

@ -59,7 +59,7 @@ module API
optional :access_level, type: Integer, values: Gitlab::Access.all_values, desc: 'A valid access level (defaults: `30`, developer access level)'
optional :expires_at, type: DateTime, desc: 'Date string in ISO 8601 format (`YYYY-MM-DDTHH:MM:SSZ`)'
end
put ":id/invitations/:email", requirements: { email: /[^\/]+/ } do
put ":id/invitations/:email", requirements: { email: %r{[^/]+} } do
source = find_source(source_type, params.delete(:id))
invite_email = params[:email]
authorize_admin_source!(source_type, source)
@ -88,7 +88,7 @@ module API
params do
requires :email, type: String, desc: 'The email address of the invitation'
end
delete ":id/invitations/:email", requirements: { email: /[^\/]+/ } do
delete ":id/invitations/:email", requirements: { email: %r{[^/]+} } do
source = find_source(source_type, params[:id])
invite_email = params[:email]
authorize_admin_source!(source_type, source)

View File

@ -2,4 +2,43 @@
module Backup
Error = Class.new(StandardError)
class FileBackupError < Backup::Error
attr_reader :app_files_dir, :backup_tarball
def initialize(app_files_dir, backup_tarball)
@app_files_dir = app_files_dir
@backup_tarball = backup_tarball
end
def message
"Failed to create compressed file '#{backup_tarball}' when trying to backup the following paths: '#{app_files_dir}'"
end
end
class RepositoryBackupError < Backup::Error
attr_reader :container, :backup_repos_path
def initialize(container, backup_repos_path)
@container = container
@backup_repos_path = backup_repos_path
end
def message
"Failed to create compressed file '#{backup_repos_path}' when trying to backup the following paths: '#{container.disk_path}'"
end
end
class DatabaseBackupError < Backup::Error
attr_reader :config, :db_file_name
def initialize(config, db_file_name)
@config = config
@db_file_name = db_file_name
end
def message
"Failed to create compressed file '#{db_file_name}' when trying to backup the main database:\n - host: '#{config[:host]}'\n - port: '#{config[:port]}'\n - database: '#{config[:database]}'"
end
end
end

View File

@ -0,0 +1,186 @@
# frozen_string_literal: true
module Gitlab
module Analytics
module CycleAnalytics
class RequestParams
include ActiveModel::Model
include ActiveModel::Validations
include ActiveModel::Attributes
include Gitlab::Utils::StrongMemoize
MAX_RANGE_DAYS = 180.days.freeze
DEFAULT_DATE_RANGE = 29.days # 30 including Date.today
STRONG_PARAMS_DEFINITION = [
:created_before,
:created_after,
:author_username,
:milestone_title,
:sort,
:direction,
:page,
:stage_id,
:end_event_filter,
label_name: [].freeze,
assignee_username: [].freeze,
project_ids: [].freeze
].freeze
FINDER_PARAM_NAMES = [
:assignee_username,
:author_username,
:milestone_title,
:label_name
].freeze
attr_writer :project_ids
attribute :created_after, :datetime
attribute :created_before, :datetime
attribute :group
attribute :current_user
attribute :value_stream
attribute :sort
attribute :direction
attribute :page
attribute :project
attribute :stage_id
attribute :end_event_filter
FINDER_PARAM_NAMES.each do |param_name|
attribute param_name
end
validates :created_after, presence: true
validates :created_before, presence: true
validate :validate_created_before
validate :validate_date_range
def initialize(params = {})
super(params)
self.created_before = (self.created_before || Time.current).at_end_of_day
self.created_after = (created_after || default_created_after).at_beginning_of_day
self.end_event_filter ||= Gitlab::Analytics::CycleAnalytics::BaseQueryBuilder::DEFAULT_END_EVENT_FILTER
end
def project_ids
Array(@project_ids)
end
def to_data_collector_params
{
current_user: current_user,
from: created_after,
to: created_before,
project_ids: project_ids,
sort: sort&.to_sym,
direction: direction&.to_sym,
page: page,
end_event_filter: end_event_filter.to_sym
}.merge(attributes.symbolize_keys.slice(*FINDER_PARAM_NAMES))
end
def to_data_attributes
{}.tap do |attrs|
attrs[:group] = group_data_attributes if group
attrs[:value_stream] = value_stream_data_attributes.to_json if value_stream
attrs[:created_after] = created_after.to_date.iso8601
attrs[:created_before] = created_before.to_date.iso8601
attrs[:projects] = group_projects(project_ids) if group && project_ids.present?
attrs[:labels] = label_name.to_json if label_name.present?
attrs[:assignees] = assignee_username.to_json if assignee_username.present?
attrs[:author] = author_username if author_username.present?
attrs[:milestone] = milestone_title if milestone_title.present?
attrs[:sort] = sort if sort.present?
attrs[:direction] = direction if direction.present?
attrs[:stage] = stage_data_attributes.to_json if stage_id.present?
end
end
private
def group_data_attributes
{
id: group.id,
name: group.name,
parent_id: group.parent_id,
full_path: group.full_path,
avatar_url: group.avatar_url
}
end
def value_stream_data_attributes
{
id: value_stream.id,
name: value_stream.name,
is_custom: value_stream.custom?
}
end
def group_projects(project_ids)
GroupProjectsFinder.new(
group: group,
current_user: current_user,
options: { include_subgroups: true },
project_ids_relation: project_ids
)
.execute
.with_route
.map { |project| project_data_attributes(project) }
.to_json
end
def project_data_attributes(project)
{
id: project.to_gid.to_s,
name: project.name,
path_with_namespace: project.path_with_namespace,
avatar_url: project.avatar_url
}
end
def stage_data_attributes
return unless stage
{
id: stage.id || stage.name,
title: stage.name
}
end
def validate_created_before
return if created_after.nil? || created_before.nil?
errors.add(:created_before, :invalid) if created_after > created_before
end
def validate_date_range
return if created_after.nil? || created_before.nil?
if (created_before - created_after) > MAX_RANGE_DAYS
errors.add(:created_after, s_('CycleAnalytics|The given date range is larger than 180 days'))
end
end
def default_created_after
if created_before
(created_before - DEFAULT_DATE_RANGE)
else
DEFAULT_DATE_RANGE.ago
end
end
def stage
return unless value_stream
strong_memoize(:stage) do
::Analytics::CycleAnalytics::StageFinder.new(parent: project || group, stage_id: stage_id).execute if stage_id
end
end
end
end
end
end

View File

@ -11,7 +11,7 @@ module Gitlab
PATTERN = %r{^\/([^\/]|\\/)+[^\\]\/[ismU]*}.freeze
def initialize(regexp)
super(regexp.gsub(/\\\//, '/'))
super(regexp.gsub(%r{\\/}, '/'))
unless Gitlab::UntrustedRegexp::RubySyntax.valid?(@value)
raise Lexer::SyntaxError, 'Invalid regular expression!'

View File

@ -23,7 +23,7 @@ module Gitlab
# 'raw' holds the Gitlab::Git::Conflict::File that this instance wraps
attr_reader :raw
delegate :type, :content, :path, :their_path, :our_path, :our_mode, :our_blob, :repository, to: :raw
delegate :type, :content, :path, :ancestor_path, :their_path, :our_path, :our_mode, :our_blob, :repository, to: :raw
def initialize(raw, merge_request:)
@raw = raw
@ -227,6 +227,26 @@ module Gitlab
new_path: our_path)
end
def conflict_type(diff_file)
if ancestor_path.present?
if our_path.present? && their_path.present?
:both_modified
elsif their_path.blank?
:modified_source_removed_target
else
:modified_target_removed_source
end
else
if our_path.present? && their_path.present?
:both_added
elsif their_path.blank?
diff_file.renamed_file? ? :renamed_same_file : :removed_target_renamed_source
else
:removed_source_renamed_target
end
end
end
private
def map_raw_lines(raw_lines)

View File

@ -67,11 +67,11 @@ module Gitlab
end
end
def progress
def progress(current: series + 1, total: total_series, track_name: track.to_s.humanize)
if Gitlab.com?
s_('InProductMarketing|This is email %{current_series} of %{total_series} in the %{track} series.') % { current_series: series + 1, total_series: total_series, track: track.to_s.humanize }
s_('InProductMarketing|This is email %{current_series} of %{total_series} in the %{track} series.') % { current_series: current, total_series: total, track: track_name }
else
s_('InProductMarketing|This is email %{current_series} of %{total_series} in the %{track} series. To disable notification emails sent by your local GitLab instance, either contact your administrator or %{unsubscribe_link}.') % { current_series: series + 1, total_series: total_series, track: track.to_s.humanize, unsubscribe_link: unsubscribe_link }
s_('InProductMarketing|This is email %{current_series} of %{total_series} in the %{track} series. To disable notification emails sent by your local GitLab instance, either contact your administrator or %{unsubscribe_link}.') % { current_series: current, total_series: total, track: track_name, unsubscribe_link: unsubscribe_link }
end
end
@ -109,7 +109,7 @@ module Gitlab
private
def track
self.class.name.demodulize.downcase.to_sym
self.class.name.demodulize.underscore.to_sym
end
def total_series

View File

@ -73,6 +73,10 @@ module Gitlab
s_('InProductMarketing|Invite your team now')
][series]
end
def progress
super(current: series + 2, total: 4)
end
end
end
end

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
module Gitlab
module Email
module Message
module InProductMarketing
class TeamShort < Base
def subject_line
s_('InProductMarketing|Team up in GitLab for greater efficiency')
end
def tagline
nil
end
def title
s_('InProductMarketing|Turn coworkers into collaborators')
end
def subtitle
s_('InProductMarketing|Invite your team today to build better code (and processes) together')
end
def body_line1
''
end
def body_line2
''
end
def cta_text
s_('InProductMarketing|Invite your colleagues today')
end
def progress
super(total: 4, track_name: 'Team')
end
def logo_path
'mailers/in_product_marketing/team-0.png'
end
end
end
end
end
end

View File

@ -6,13 +6,14 @@ module Gitlab
class File
UnsupportedEncoding = Class.new(StandardError)
attr_reader :their_path, :our_path, :our_mode, :repository, :commit_oid
attr_reader :ancestor_path, :their_path, :our_path, :our_mode, :repository, :commit_oid
attr_accessor :raw_content
def initialize(repository, commit_oid, conflict, raw_content)
@repository = repository
@commit_oid = commit_oid
@ancestor_path = conflict[:ancestor][:path]
@their_path = conflict[:theirs][:path]
@our_path = conflict[:ours][:path]
@our_mode = conflict[:ours][:mode]

View File

@ -43,6 +43,7 @@ module Gitlab
def conflict_from_gitaly_file_header(header)
{
ancestor: { path: header.ancestor_path },
ours: { path: header.our_path, mode: header.our_mode },
theirs: { path: header.their_path }
}

View File

@ -13,7 +13,7 @@ module Gitlab
"put" => %w(200 202 204 400 401 403 404 405 406 409 410 422 500)
}.freeze
HEALTH_ENDPOINT = /^\/-\/(liveness|readiness|health|metrics)\/?$/.freeze
HEALTH_ENDPOINT = %r{^/-/(liveness|readiness|health|metrics)/?$}.freeze
FEATURE_CATEGORY_DEFAULT = 'unknown'

View File

@ -9,7 +9,7 @@ module Gitlab
IGNORABLE_SQL = %w{BEGIN COMMIT}.freeze
DB_COUNTERS = %i{db_count db_write_count db_cached_count}.freeze
SQL_COMMANDS_WITH_COMMENTS_REGEX = /\A(\/\*.*\*\/\s)?((?!(.*[^\w'"](DELETE|UPDATE|INSERT INTO)[^\w'"])))(WITH.*)?(SELECT)((?!(FOR UPDATE|FOR SHARE)).)*$/i.freeze
SQL_COMMANDS_WITH_COMMENTS_REGEX = %r{\A(/\*.*\*/\s)?((?!(.*[^\w'"](DELETE|UPDATE|INSERT INTO)[^\w'"])))(WITH.*)?(SELECT)((?!(FOR UPDATE|FOR SHARE)).)*$}i.freeze
SQL_DURATION_BUCKET = [0.05, 0.1, 0.25].freeze
TRANSACTION_DURATION_BUCKET = [0.1, 0.25, 1].freeze

View File

@ -184,19 +184,19 @@ module Gitlab
# - Must not have a scheme, such as http:// or https://
# - Must not have a port number, such as :8080 or :8443
@go_package_regex ||= /
@go_package_regex ||= %r{
\b (?# word boundary)
(?<domain>
[0-9a-z](?:(?:-|[0-9a-z]){0,61}[0-9a-z])? (?# first domain)
(?:\.[0-9a-z](?:(?:-|[0-9a-z]){0,61}[0-9a-z])?)* (?# inner domains)
\.[a-z]{2,} (?# top-level domain)
)
(?<path>\/(?:
[-\/$_.+!*'(),0-9a-z] (?# plain URL character)
(?<path>/(?:
[-/$_.+!*'(),0-9a-z] (?# plain URL character)
| %[0-9a-f]{2})* (?# URL encoded character)
)? (?# path)
\b (?# word boundary)
/ix.freeze
}ix.freeze
end
def generic_package_version_regex
@ -416,7 +416,7 @@ module Gitlab
end
def base64_regex
@base64_regex ||= /(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?/.freeze
@base64_regex ||= %r{(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?}.freeze
end
def feature_flag_regex

View File

@ -15,7 +15,8 @@ module Gitlab
MergeRequestCounter,
DesignsCounter,
KubernetesAgentCounter,
StaticSiteEditorCounter
StaticSiteEditorCounter,
DiffsCounter
].freeze
UsageDataCounterError = Class.new(StandardError)

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
module Gitlab
module UsageDataCounters
class DiffsCounter < BaseCounter
KNOWN_EVENTS = %w[searches].freeze
PREFIX = 'diff'
end
end
end

View File

@ -232,3 +232,8 @@
redis_slot: code_review
category: code_review
aggregation: weekly
- name: i_code_review_user_searches_diff
redis_slot: code_review
category: code_review
aggregation: weekly
feature_flag: diff_searching_usage_data

View File

@ -13,7 +13,7 @@ module Gitlab
return unless path.is_a?(String)
path = decode_path(path)
path_regex = /(\A(\.{1,2})\z|\A\.\.[\/\\]|[\/\\]\.\.\z|[\/\\]\.\.[\/\\]|\n)/
path_regex = %r{(\A(\.{1,2})\z|\A\.\.[/\\]|[/\\]\.\.\z|[/\\]\.\.[/\\]|\n)}
if path.match?(path_regex)
raise PathTraversalAttackError, 'Invalid path'

View File

@ -6,6 +6,6 @@ module ProductAnalytics
URL = Gitlab.config.gitlab.url + '/-/sp.js'
# The collector URL minus protocol and /i
COLLECTOR_URL = Gitlab.config.gitlab.url.sub(/\Ahttps?\:\/\//, '') + '/-/collector'
COLLECTOR_URL = Gitlab.config.gitlab.url.sub(%r{\Ahttps?\://}, '') + '/-/collector'
end
end

View File

@ -8612,7 +8612,7 @@ msgstr ""
msgid "ContainerRegistry|Delete selected tags"
msgstr ""
msgid "ContainerRegistry|Deleting the image repository will delete all images and tags inside. This action cannot be undone."
msgid "ContainerRegistry|Deleting the image repository will delete all images and tags inside. This action cannot be undone. Please type the following to confirm: %{code}"
msgstr ""
msgid "ContainerRegistry|Deletion disabled due to missing or insufficient permissions."
@ -10918,30 +10918,30 @@ msgstr ""
msgid "DeployTokens|Active Deploy Tokens (%{active_tokens})"
msgstr ""
msgid "DeployTokens|Add a deploy token"
msgid "DeployTokens|Allows read and write access to registry images."
msgstr ""
msgid "DeployTokens|Allows read access to the package registry."
msgid "DeployTokens|Allows read and write access to the package registry."
msgstr ""
msgid "DeployTokens|Allows read-only access to registry images."
msgstr ""
msgid "DeployTokens|Allows read-only access to the package registry."
msgstr ""
msgid "DeployTokens|Allows read-only access to the repository."
msgstr ""
msgid "DeployTokens|Allows write access to registry images."
msgstr ""
msgid "DeployTokens|Allows write access to the package registry."
msgstr ""
msgid "DeployTokens|Copy deploy token"
msgstr ""
msgid "DeployTokens|Copy username"
msgstr ""
msgid "DeployTokens|Create a new deploy token for all projects in this group. %{link_start}What are deploy tokens?%{link_end}"
msgstr ""
msgid "DeployTokens|Create deploy token"
msgstr ""
@ -10954,6 +10954,15 @@ msgstr ""
msgid "DeployTokens|Deploy tokens allow access to packages, your repository, and registry images."
msgstr ""
msgid "DeployTokens|Enter a unique name for your deploy token."
msgstr ""
msgid "DeployTokens|Enter a username for your token. Defaults to %{code_start}gitlab+deploy-token-{n}%{code_end}."
msgstr ""
msgid "DeployTokens|Enter an expiration date for your token. Defaults to never expire."
msgstr ""
msgid "DeployTokens|Expires"
msgstr ""
@ -10963,7 +10972,7 @@ msgstr ""
msgid "DeployTokens|Name"
msgstr ""
msgid "DeployTokens|Pick a name for your unique deploy token."
msgid "DeployTokens|New deploy token"
msgstr ""
msgid "DeployTokens|Revoke"
@ -10984,12 +10993,6 @@ msgstr ""
msgid "DeployTokens|This username supports access. %{link_start}What kind of access?%{link_end}"
msgstr ""
msgid "DeployTokens|Unless you enter a date, the token does not expire."
msgstr ""
msgid "DeployTokens|Unless you specify a username, it is set to \"gitlab+deploy-token-{n}\"."
msgstr ""
msgid "DeployTokens|Use this token as a password. Save it. This password can %{i_start}not%{i_end} be recovered."
msgstr ""
@ -13333,6 +13336,9 @@ msgstr ""
msgid "Expiration date"
msgstr ""
msgid "Expiration date (optional)"
msgstr ""
msgid "Expired"
msgstr ""
@ -13345,9 +13351,6 @@ msgstr ""
msgid "Expires"
msgstr ""
msgid "Expires at (optional)"
msgstr ""
msgid "Expires in %{expires_at}"
msgstr ""
@ -17001,6 +17004,9 @@ msgstr ""
msgid "InProductMarketing|Invite your team now"
msgstr ""
msgid "InProductMarketing|Invite your team today to build better code (and processes) together"
msgstr ""
msgid "InProductMarketing|It's all in the stats"
msgstr ""
@ -17082,6 +17088,9 @@ msgstr ""
msgid "InProductMarketing|Take your source code management to the next level"
msgstr ""
msgid "InProductMarketing|Team up in GitLab for greater efficiency"
msgstr ""
msgid "InProductMarketing|Team work makes the dream work"
msgstr ""
@ -17118,6 +17127,9 @@ msgstr ""
msgid "InProductMarketing|Try it yourself"
msgstr ""
msgid "InProductMarketing|Turn coworkers into collaborators"
msgstr ""
msgid "InProductMarketing|Twitter"
msgstr ""
@ -25701,9 +25713,6 @@ msgstr ""
msgid "ProjectSettings|Allow"
msgstr ""
msgid "ProjectSettings|Allow editing commit messages"
msgstr ""
msgid "ProjectSettings|Always show thumbs-up and thumbs-down award emoji buttons on issues, merge requests, and snippets."
msgstr ""
@ -25731,9 +25740,6 @@ msgstr ""
msgid "ProjectSettings|Choose your merge method, merge options, merge checks, merge suggestions, and set up a default description template for merge requests."
msgstr ""
msgid "ProjectSettings|Commit authors can edit commit messages on unprotected branches."
msgstr ""
msgid "ProjectSettings|Configure your project resources and monitor their health."
msgstr ""
@ -28655,7 +28661,7 @@ msgstr ""
msgid "Scopes"
msgstr ""
msgid "Scopes [Select 1 or more]"
msgid "Scopes (select at least one)"
msgstr ""
msgid "Scopes can't be blank"
@ -38304,9 +38310,6 @@ msgstr ""
msgid "can only have one escalation policy"
msgstr ""
msgid "can't be enabled because signed commits are required for this project"
msgstr ""
msgid "can't be the same as the source project"
msgstr ""

View File

@ -7,26 +7,58 @@ RSpec.describe Projects::Analytics::CycleAnalytics::StagesController do
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, group: group) }
let(:params) { { namespace_id: group, project_id: project, value_stream_id: 'default' } }
let(:params) do
{
namespace_id: group,
project_id: project,
value_stream_id: Analytics::CycleAnalytics::Stages::BaseService::DEFAULT_VALUE_STREAM_NAME
}
end
before do
sign_in(user)
end
describe 'GET index' do
context 'when user is member of the project' do
shared_examples 'project-level value stream analytics endpoint' do
before do
project.add_developer(user)
end
it 'succeeds' do
get action, params: params
expect(response).to have_gitlab_http_status(:ok)
end
end
shared_examples 'project-level value stream analytics request error examples' do
context 'when invalid value stream id is given' do
before do
project.add_developer(user)
params[:value_stream_id] = 1
end
it 'succeeds' do
get :index, params: params
it 'renders 404' do
get action, params: params
expect(response).to have_gitlab_http_status(:ok)
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when user is not member of the project' do
it 'renders 404' do
get action, params: params
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe 'GET index' do
let(:action) { :index }
it_behaves_like 'project-level value stream analytics endpoint' do
it 'exposes the default stages' do
get :index, params: params
get action, params: params
expect(json_response['stages'].size).to eq(Gitlab::Analytics::CycleAnalytics::DefaultStages.all.size)
end
@ -37,31 +69,109 @@ RSpec.describe Projects::Analytics::CycleAnalytics::StagesController do
expect(list_service).to receive(:allowed?).and_return(false)
end
get :index, params: params
get action, params: params
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'when invalid value stream id is given' do
before do
params[:value_stream_id] = 1
end
it_behaves_like 'project-level value stream analytics request error examples'
end
it 'renders 404' do
get :index, params: params
describe 'GET median' do
let(:action) { :median }
expect(response).to have_gitlab_http_status(:not_found)
before do
params[:id] = 'issue'
end
it_behaves_like 'project-level value stream analytics endpoint' do
it 'returns the median' do
result = 2
expect_next_instance_of(Gitlab::Analytics::CycleAnalytics::Median) do |instance|
expect(instance).to receive(:seconds).and_return(result)
end
get action, params: params
expect(json_response['value']).to eq(result)
end
end
context 'when user is not member of the project' do
it 'renders 404' do
get :index, params: params
it_behaves_like 'project-level value stream analytics request error examples'
end
expect(response).to have_gitlab_http_status(:not_found)
describe 'GET average' do
let(:action) { :average }
before do
params[:id] = 'issue'
end
it_behaves_like 'project-level value stream analytics endpoint' do
it 'returns the average' do
result = 2
expect_next_instance_of(Gitlab::Analytics::CycleAnalytics::Average) do |instance|
expect(instance).to receive(:seconds).and_return(result)
end
get action, params: params
expect(json_response['value']).to eq(result)
end
end
it_behaves_like 'project-level value stream analytics request error examples'
end
describe 'GET count' do
let(:action) { :count }
before do
params[:id] = 'issue'
end
it_behaves_like 'project-level value stream analytics endpoint' do
it 'returns the count' do
count = 2
expect_next_instance_of(Gitlab::Analytics::CycleAnalytics::DataCollector) do |instance|
expect(instance).to receive(:count).and_return(count)
end
get action, params: params
expect(json_response['count']).to eq(count)
end
end
it_behaves_like 'project-level value stream analytics request error examples'
end
describe 'GET records' do
let(:action) { :records }
before do
params[:id] = 'issue'
end
it_behaves_like 'project-level value stream analytics endpoint' do
it 'returns the records' do
result = Issue.none.page(1)
expect_next_instance_of(Gitlab::Analytics::CycleAnalytics::RecordsFetcher) do |instance|
expect(instance).to receive(:serialized_records).and_yield(result).and_return([])
end
get action, params: params
expect(json_response).to eq([])
end
end
it_behaves_like 'project-level value stream analytics request error examples'
end
end

View File

@ -141,6 +141,24 @@ RSpec.describe Projects::MergeRequests::DiffsController do
end
describe 'GET diffs_metadata' do
shared_examples_for 'serializes diffs metadata with expected arguments' do
it 'returns success' do
subject
expect(response).to have_gitlab_http_status(:ok)
end
it 'serializes paginated merge request diff collection' do
expect_next_instance_of(DiffsMetadataSerializer) do |instance|
expect(instance).to receive(:represent)
.with(an_instance_of(collection), expected_options)
.and_call_original
end
subject
end
end
def go(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
@ -179,32 +197,25 @@ RSpec.describe Projects::MergeRequests::DiffsController do
end
context 'with valid diff_id' do
it 'returns success' do
go(diff_id: merge_request.merge_request_diff.id)
subject { go(diff_id: merge_request.merge_request_diff.id) }
expect(response).to have_gitlab_http_status(:ok)
end
it 'serializes diffs metadata with expected arguments' do
expected_options = {
environment: nil,
merge_request: merge_request,
merge_request_diff: merge_request.merge_request_diff,
merge_request_diffs: merge_request.merge_request_diffs,
start_version: nil,
start_sha: nil,
commit: nil,
latest_diff: true,
only_context_commits: false
}
expect_next_instance_of(DiffsMetadataSerializer) do |instance|
expect(instance).to receive(:represent)
.with(an_instance_of(Gitlab::Diff::FileCollection::MergeRequestDiff), expected_options)
.and_call_original
it_behaves_like 'serializes diffs metadata with expected arguments' do
let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiff }
let(:expected_options) do
{
environment: nil,
merge_request: merge_request,
merge_request_diff: merge_request.merge_request_diff,
merge_request_diffs: merge_request.merge_request_diffs,
start_version: nil,
start_sha: nil,
commit: nil,
latest_diff: true,
only_context_commits: false,
allow_tree_conflicts: true,
merge_ref_head_diff: false
}
end
go(diff_id: merge_request.merge_request_diff.id)
end
end
@ -261,62 +272,75 @@ RSpec.describe Projects::MergeRequests::DiffsController do
end
context 'with MR regular diff params' do
it 'returns success' do
go
subject { go }
expect(response).to have_gitlab_http_status(:ok)
end
it 'serializes diffs metadata with expected arguments' do
expected_options = {
environment: nil,
merge_request: merge_request,
merge_request_diff: merge_request.merge_request_diff,
merge_request_diffs: merge_request.merge_request_diffs,
start_version: nil,
start_sha: nil,
commit: nil,
latest_diff: true,
only_context_commits: false
}
expect_next_instance_of(DiffsMetadataSerializer) do |instance|
expect(instance).to receive(:represent)
.with(an_instance_of(Gitlab::Diff::FileCollection::MergeRequestDiff), expected_options)
.and_call_original
it_behaves_like 'serializes diffs metadata with expected arguments' do
let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiff }
let(:expected_options) do
{
environment: nil,
merge_request: merge_request,
merge_request_diff: merge_request.merge_request_diff,
merge_request_diffs: merge_request.merge_request_diffs,
start_version: nil,
start_sha: nil,
commit: nil,
latest_diff: true,
only_context_commits: false,
allow_tree_conflicts: true,
merge_ref_head_diff: nil
}
end
go
end
end
context 'with commit param' do
it 'returns success' do
go(commit_id: merge_request.diff_head_sha)
subject { go(commit_id: merge_request.diff_head_sha) }
expect(response).to have_gitlab_http_status(:ok)
it_behaves_like 'serializes diffs metadata with expected arguments' do
let(:collection) { Gitlab::Diff::FileCollection::Commit }
let(:expected_options) do
{
environment: nil,
merge_request: merge_request,
merge_request_diff: nil,
merge_request_diffs: merge_request.merge_request_diffs,
start_version: nil,
start_sha: nil,
commit: merge_request.diff_head_commit,
latest_diff: nil,
only_context_commits: false,
allow_tree_conflicts: true,
merge_ref_head_diff: nil
}
end
end
end
context 'when display_merge_conflicts_in_diff is disabled' do
subject { go }
before do
stub_feature_flags(display_merge_conflicts_in_diff: false)
end
it 'serializes diffs metadata with expected arguments' do
expected_options = {
environment: nil,
merge_request: merge_request,
merge_request_diff: nil,
merge_request_diffs: merge_request.merge_request_diffs,
start_version: nil,
start_sha: nil,
commit: merge_request.diff_head_commit,
latest_diff: nil,
only_context_commits: false
}
expect_next_instance_of(DiffsMetadataSerializer) do |instance|
expect(instance).to receive(:represent)
.with(an_instance_of(Gitlab::Diff::FileCollection::Commit), expected_options)
.and_call_original
it_behaves_like 'serializes diffs metadata with expected arguments' do
let(:collection) { Gitlab::Diff::FileCollection::MergeRequestDiff }
let(:expected_options) do
{
environment: nil,
merge_request: merge_request,
merge_request_diff: merge_request.merge_request_diff,
merge_request_diffs: merge_request.merge_request_diffs,
start_version: nil,
start_sha: nil,
commit: nil,
latest_diff: true,
only_context_commits: false,
allow_tree_conflicts: false,
merge_ref_head_diff: nil
}
end
go(commit_id: merge_request.diff_head_sha)
end
end
end

View File

@ -767,8 +767,7 @@ RSpec.describe ProjectsController do
id: project.path,
project: {
project_setting_attributes: {
show_default_award_emojis: boolean_value,
allow_editing_commit_messages: boolean_value
show_default_award_emojis: boolean_value
}
}
}
@ -776,7 +775,6 @@ RSpec.describe ProjectsController do
project.reload
expect(project.show_default_award_emojis?).to eq(result)
expect(project.allow_editing_commit_messages?).to eq(result)
end
end
end

View File

@ -53,6 +53,20 @@ RSpec.describe SearchController do
end
end
shared_examples_for 'support for active record query timeouts' do |action, params, method_to_stub, format|
before do
allow_next_instance_of(SearchService) do |service|
allow(service).to receive(method_to_stub).and_raise(ActiveRecord::QueryCanceled)
end
end
it 'renders a 408 when a timeout occurs' do
get action, params: params, format: format
expect(response).to have_gitlab_http_status(:request_timeout)
end
end
describe 'GET #show' do
it_behaves_like 'when the user cannot read cross project', :show, { search: 'hello' } do
it 'still allows accessing the search page' do
@ -63,6 +77,7 @@ RSpec.describe SearchController do
end
it_behaves_like 'with external authorization service enabled', :show, { search: 'hello' }
it_behaves_like 'support for active record query timeouts', :show, { search: 'hello' }, :search_objects, :html
context 'uses the right partials depending on scope' do
using RSpec::Parameterized::TableSyntax
@ -230,6 +245,7 @@ RSpec.describe SearchController do
describe 'GET #count' do
it_behaves_like 'when the user cannot read cross project', :count, { search: 'hello', scope: 'projects' }
it_behaves_like 'with external authorization service enabled', :count, { search: 'hello', scope: 'projects' }
it_behaves_like 'support for active record query timeouts', :count, { search: 'hello', scope: 'projects' }, :search_results, :json
it 'returns the result count for the given term and scope' do
create(:project, :public, name: 'hello world')

View File

@ -27,7 +27,6 @@ const defaultProps = {
emailsDisabled: false,
packagesEnabled: true,
showDefaultAwardEmojis: true,
allowEditingCommitMessages: false,
},
isGitlabCom: true,
canDisableEmails: true,
@ -53,7 +52,7 @@ describe('Settings Panel', () => {
let wrapper;
const mountComponent = (
{ currentSettings = {}, glFeatures = {}, ...customProps } = {},
{ currentSettings = {}, ...customProps } = {},
mountFn = shallowMount,
) => {
const propsData = {
@ -64,9 +63,6 @@ describe('Settings Panel', () => {
return mountFn(settingsPanel, {
propsData,
provide: {
glFeatures,
},
});
};
@ -100,8 +96,6 @@ describe('Settings Panel', () => {
const findShowDefaultAwardEmojis = () =>
wrapper.find('input[name="project[project_setting_attributes][show_default_award_emojis]"]');
const findMetricsVisibilitySettings = () => wrapper.find({ ref: 'metrics-visibility-settings' });
const findAllowEditingCommitMessages = () =>
wrapper.find({ ref: 'allow-editing-commit-messages' }).exists();
const findOperationsSettings = () => wrapper.find({ ref: 'operations-settings' });
afterEach(() => {
@ -582,18 +576,6 @@ describe('Settings Panel', () => {
);
});
describe('Settings panel with feature flags', () => {
describe('Allow edit of commit message', () => {
it('should show the allow editing of commit messages checkbox', () => {
wrapper = mountComponent({
glFeatures: { allowEditingCommitMessages: true },
});
expect(findAllowEditingCommitMessages()).toBe(true);
});
});
});
describe('Analytics', () => {
it('should show the analytics toggle', () => {
wrapper = mountComponent();

View File

@ -1,5 +1,6 @@
import { GlSprintf } from '@gitlab/ui';
import { GlSprintf, GlFormInput } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import { nextTick } from 'vue';
import component from '~/registry/explorer/components/details_page/delete_modal.vue';
import {
REMOVE_TAG_CONFIRMATION_TEXT,
@ -12,8 +13,9 @@ import { GlModal } from '../../stubs';
describe('Delete Modal', () => {
let wrapper;
const findModal = () => wrapper.find(GlModal);
const findModal = () => wrapper.findComponent(GlModal);
const findDescription = () => wrapper.find('[data-testid="description"]');
const findInputComponent = () => wrapper.findComponent(GlFormInput);
const mountComponent = (propsData) => {
wrapper = shallowMount(component, {
@ -25,6 +27,13 @@ describe('Delete Modal', () => {
});
};
const expectPrimaryActionStatus = (disabled = true) =>
expect(findModal().props('actionPrimary')).toMatchObject(
expect.objectContaining({
attributes: [{ variant: 'danger' }, { disabled }],
}),
);
afterEach(() => {
wrapper.destroy();
wrapper = null;
@ -65,11 +74,49 @@ describe('Delete Modal', () => {
it('has the correct description', () => {
mountComponent({ deleteImage: true });
expect(wrapper.text()).toContain(DELETE_IMAGE_CONFIRMATION_TEXT);
expect(wrapper.text()).toContain(
DELETE_IMAGE_CONFIRMATION_TEXT.replace('%{code}', '').trim(),
);
});
describe('delete button', () => {
const itemsToBeDeleted = [{ project: { path: 'foo' } }];
it('is disabled by default', () => {
mountComponent({ deleteImage: true });
expectPrimaryActionStatus();
});
it('if the user types something different from the project path is disabled', async () => {
mountComponent({ deleteImage: true, itemsToBeDeleted });
findInputComponent().vm.$emit('input', 'bar');
await nextTick();
expectPrimaryActionStatus();
});
it('if the user types the project path it is enabled', async () => {
mountComponent({ deleteImage: true, itemsToBeDeleted });
findInputComponent().vm.$emit('input', 'foo');
await nextTick();
expectPrimaryActionStatus(false);
});
});
});
describe('when we are deleting tags', () => {
it('delete button is enabled', () => {
mountComponent();
expectPrimaryActionStatus(false);
});
describe('itemsToBeDeleted contains one element', () => {
beforeEach(() => {
mountComponent({ itemsToBeDeleted: [{ path: 'foo' }] });

View File

@ -1,10 +1,11 @@
import { GlButton, GlIcon } from '@gitlab/ui';
import { GlDropdownItem, GlIcon } from '@gitlab/ui';
import { shallowMount, createLocalVue } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import { useFakeDate } from 'helpers/fake_date';
import createMockApollo from 'helpers/mock_apollo_helper';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import waitForPromises from 'helpers/wait_for_promises';
import { GlDropdown } from 'jest/registry/explorer/stubs';
import component from '~/registry/explorer/components/details_page/details_header.vue';
import {
UNSCHEDULED_STATUS,
@ -48,8 +49,8 @@ describe('Details Header', () => {
const findTitle = () => findByTestId('title');
const findTagsCount = () => findByTestId('tags-count');
const findCleanup = () => findByTestId('cleanup');
const findDeleteButton = () => wrapper.find(GlButton);
const findInfoIcon = () => wrapper.find(GlIcon);
const findDeleteButton = () => wrapper.findComponent(GlDropdownItem);
const findInfoIcon = () => wrapper.findComponent(GlIcon);
const waitForMetadataItems = async () => {
// Metadata items are printed by a loop in the title-area and it takes two ticks for them to be available
@ -84,6 +85,8 @@ describe('Details Header', () => {
mocks,
stubs: {
TitleArea,
GlDropdown,
GlDropdownItem,
},
});
};
@ -152,10 +155,11 @@ describe('Details Header', () => {
it('has the correct props', () => {
mountComponent();
expect(findDeleteButton().props()).toMatchObject({
variant: 'danger',
disabled: false,
});
expect(findDeleteButton().attributes()).toMatchObject(
expect.objectContaining({
variant: 'danger',
}),
);
});
it('emits the correct event', () => {
@ -168,16 +172,16 @@ describe('Details Header', () => {
it.each`
canDelete | disabled | isDisabled
${true} | ${false} | ${false}
${true} | ${true} | ${true}
${false} | ${false} | ${true}
${false} | ${true} | ${true}
${true} | ${false} | ${undefined}
${true} | ${true} | ${'true'}
${false} | ${false} | ${'true'}
${false} | ${true} | ${'true'}
`(
'when canDelete is $canDelete and disabled is $disabled is $isDisabled that the button is disabled',
({ canDelete, disabled, isDisabled }) => {
mountComponent({ propsData: { image: { ...defaultImage, canDelete }, disabled } });
expect(findDeleteButton().props('disabled')).toBe(isDisabled);
expect(findDeleteButton().attributes('disabled')).toBe(isDisabled);
},
);
});

View File

@ -119,6 +119,7 @@ export const containerRepositoryMock = {
expirationPolicyCleanupStatus: 'UNSCHEDULED',
project: {
visibility: 'public',
path: 'gitlab-test',
containerExpirationPolicy: {
enabled: false,
nextRunAt: '2020-11-27T08:59:27Z',

View File

@ -2,6 +2,7 @@ import {
GlModal as RealGlModal,
GlEmptyState as RealGlEmptyState,
GlSkeletonLoader as RealGlSkeletonLoader,
GlDropdown as RealGlDropdown,
} from '@gitlab/ui';
import { RouterLinkStub } from '@vue/test-utils';
import { stubComponent } from 'helpers/stub_component';
@ -38,3 +39,7 @@ export const ListItem = {
};
},
};
export const GlDropdown = stubComponent(RealGlDropdown, {
template: '<div><slot></slot></div>',
});

View File

@ -0,0 +1,56 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe ::Mutations::BaseMutation do
include GraphqlHelpers
describe 'argument nullability' do
let_it_be(:user) { create(:user) }
let_it_be(:context) { { current_user: user } }
subject(:mutation) { mutation_class.new(object: nil, context: context, field: nil) }
describe 'when using a mutation with correct argument declarations' do
context 'when argument is nullable and required' do
let(:mutation_class) do
Class.new(described_class) do
argument :foo, GraphQL::STRING_TYPE, required: :nullable
end
end
specify do
expect { subject.ready? }.to raise_error(ArgumentError, /must be provided: foo/)
end
specify do
expect { subject.ready?(foo: nil) }.not_to raise_error
end
specify do
expect { subject.ready?(foo: "bar") }.not_to raise_error
end
end
context 'when argument is required and NOT nullable' do
let(:mutation_class) do
Class.new(described_class) do
argument :foo, GraphQL::STRING_TYPE, required: true
end
end
specify do
expect { subject.ready? }.to raise_error(ArgumentError, /must be provided/)
end
specify do
expect { subject.ready?(foo: nil) }.to raise_error(ArgumentError, /must be provided/)
end
specify do
expect { subject.ready?(foo: "bar") }.not_to raise_error
end
end
end
end
end

View File

@ -41,7 +41,7 @@ RSpec.describe Mutations::Ci::Runner::Delete do
let(:mutation_params) { {} }
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError, "missing keyword: :id")
expect { subject }.to raise_error(ArgumentError, "Arguments must be provided: id")
end
end

View File

@ -43,7 +43,7 @@ RSpec.describe Mutations::Ci::Runner::Update do
let(:mutation_params) { {} }
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError, "missing keyword: :id")
expect { subject }.to raise_error(ArgumentError, "Arguments must be provided: id")
end
end

View File

@ -3,15 +3,41 @@
require 'spec_helper'
RSpec.describe Types::BaseArgument do
include_examples 'Gitlab-style deprecations' do
let_it_be(:field) do
Types::BaseField.new(name: 'field', type: String, null: true)
let_it_be(:field) do
Types::BaseField.new(name: 'field', type: String, null: true)
end
let(:base_args) { { name: 'test', type: String, required: false, owner: field } }
def subject(args = {})
described_class.new(**base_args.merge(args))
end
include_examples 'Gitlab-style deprecations'
describe 'required argument declarations' do
it 'accepts nullable, required arguments' do
arguments = base_args.merge({ required: :nullable })
expect { subject(arguments) }.not_to raise_error
end
let(:base_args) { { name: 'test', type: String, required: false, owner: field } }
it 'accepts required, non-nullable arguments' do
arguments = base_args.merge({ required: true })
def subject(args = {})
described_class.new(**base_args.merge(args))
expect { subject(arguments) }.not_to raise_error
end
it 'accepts non-required arguments' do
arguments = base_args.merge({ required: false })
expect { subject(arguments) }.not_to raise_error
end
it 'accepts no required argument declaration' do
arguments = base_args
expect { subject(arguments) }.not_to raise_error
end
end
end

View File

@ -917,34 +917,4 @@ RSpec.describe ProjectsHelper do
subject
end
end
describe '#project_permissions_settings' do
context 'with no project_setting associated' do
it 'includes a value for edit commit messages' do
settings = project_permissions_settings(project)
expect(settings[:allowEditingCommitMessages]).to be_falsy
end
end
context 'when commits are allowed to be edited' do
it 'includes the edit commit message value' do
project.create_project_setting(allow_editing_commit_messages: true)
settings = project_permissions_settings(project)
expect(settings[:allowEditingCommitMessages]).to be_truthy
end
end
context 'when commits are not allowed to be edited' do
it 'returns false to the edit commit message value' do
project.create_project_setting(allow_editing_commit_messages: false)
settings = project_permissions_settings(project)
expect(settings[:allowEditingCommitMessages]).to be_falsy
end
end
end
end

View File

@ -0,0 +1,30 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Backup::DatabaseBackupError do
let(:config) do
{
host: 'localhost',
port: 5432,
database: 'gitlabhq_test'
}
end
let(:db_file_name) { File.join(Gitlab.config.backup.path, 'db', 'database.sql.gz') }
subject { described_class.new(config, db_file_name) }
it { is_expected.to respond_to :config }
it { is_expected.to respond_to :db_file_name }
it 'expects exception message to include database file' do
expect(subject.message).to include("#{db_file_name}")
end
it 'expects exception message to include database paths being back-up' do
expect(subject.message).to include("#{config[:host]}")
expect(subject.message).to include("#{config[:port]}")
expect(subject.message).to include("#{config[:database]}")
end
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Backup::FileBackupError do
let_it_be(:lfs) { create(:lfs_object) }
let_it_be(:upload) { create(:upload) }
let(:backup_tarball) { '/tmp/backup/uploads' }
shared_examples 'includes backup path' do
it { is_expected.to respond_to :app_files_dir }
it { is_expected.to respond_to :backup_tarball }
it 'expects exception message to include file backup path location' do
expect(subject.message).to include("#{subject.backup_tarball}")
end
it 'expects exception message to include file being back-up' do
expect(subject.message).to include("#{subject.app_files_dir}")
end
end
context 'with lfs file' do
subject { described_class.new(lfs, backup_tarball) }
it_behaves_like 'includes backup path'
end
context 'with uploads file' do
subject { described_class.new(upload, backup_tarball) }
it_behaves_like 'includes backup path'
end
end

View File

@ -0,0 +1,42 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Backup::RepositoryBackupError do
let_it_be(:snippet) { create(:snippet, content: 'foo', file_name: 'foo') }
let_it_be(:project) { create(:project, :repository) }
let_it_be(:wiki) { ProjectWiki.new(project, nil ) }
let(:backup_repos_path) { '/tmp/backup/repositories' }
shared_examples 'includes backup path' do
it { is_expected.to respond_to :container }
it { is_expected.to respond_to :backup_repos_path }
it 'expects exception message to include repo backup path location' do
expect(subject.message).to include("#{subject.backup_repos_path}")
end
it 'expects exception message to include container being back-up' do
expect(subject.message).to include("#{subject.container.disk_path}")
end
end
context 'with snippet repository' do
subject { described_class.new(snippet, backup_repos_path) }
it_behaves_like 'includes backup path'
end
context 'with project repository' do
subject { described_class.new(project, backup_repos_path) }
it_behaves_like 'includes backup path'
end
context 'with wiki repository' do
subject { described_class.new(wiki, backup_repos_path) }
it_behaves_like 'includes backup path'
end
end

View File

@ -18,7 +18,15 @@ RSpec.describe Gitlab::Conflict::File do
let(:conflict_file) { described_class.new(raw_conflict_file, merge_request: merge_request) }
describe 'delegates' do
it { expect(conflict_file).to delegate_method(:type).to(:raw) }
it { expect(conflict_file).to delegate_method(:content).to(:raw) }
it { expect(conflict_file).to delegate_method(:path).to(:raw) }
it { expect(conflict_file).to delegate_method(:ancestor_path).to(:raw) }
it { expect(conflict_file).to delegate_method(:their_path).to(:raw) }
it { expect(conflict_file).to delegate_method(:our_path).to(:raw) }
it { expect(conflict_file).to delegate_method(:our_mode).to(:raw) }
it { expect(conflict_file).to delegate_method(:our_blob).to(:raw) }
it { expect(conflict_file).to delegate_method(:repository).to(:raw) }
end
describe '#resolve_lines' do
@ -328,4 +336,27 @@ RSpec.describe Gitlab::Conflict::File do
end
end
end
describe '#conflict_type' do
using RSpec::Parameterized::TableSyntax
let(:rugged_conflict) { { ancestor: { path: ancestor_path }, theirs: { path: their_path }, ours: { path: our_path } } }
let(:diff_file) { double(renamed_file?: renamed_file?) }
subject(:conflict_type) { conflict_file.conflict_type(diff_file) }
where(:ancestor_path, :their_path, :our_path, :renamed_file?, :result) do
'/ancestor/path' | '/their/path' | '/our/path' | false | :both_modified
'/ancestor/path' | '' | '/our/path' | false | :modified_source_removed_target
'/ancestor/path' | '/their/path' | '' | false | :modified_target_removed_source
'' | '/their/path' | '/our/path' | false | :both_added
'' | '' | '/our/path' | false | :removed_target_renamed_source
'' | '' | '/our/path' | true | :renamed_same_file
'' | '/their/path' | '' | false | :removed_source_renamed_target
end
with_them do
it { expect(conflict_type).to eq(result) }
end
end
end

View File

@ -0,0 +1,47 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Email::Message::InProductMarketing::TeamShort do
using RSpec::Parameterized::TableSyntax
let_it_be(:group) { build(:group) }
let_it_be(:user) { build(:user) }
let(:series) { 0 }
subject(:message) { described_class.new(group: group, user: user, series: series)}
describe 'public methods' do
it 'returns value for series', :aggregate_failures do
expect(message.subject_line).to eq 'Team up in GitLab for greater efficiency'
expect(message.tagline).to be_nil
expect(message.title).to eq 'Turn coworkers into collaborators'
expect(message.subtitle).to eq 'Invite your team today to build better code (and processes) together'
expect(message.body_line1).to be_empty
expect(message.body_line2).to be_empty
expect(message.cta_text).to eq 'Invite your colleagues today'
expect(message.logo_path).to eq 'mailers/in_product_marketing/team-0.png'
end
describe '#progress' do
subject { message.progress }
before do
allow(Gitlab).to receive(:com?).and_return(is_gitlab_com)
end
context 'on gitlab.com' do
let(:is_gitlab_com) { true }
it { is_expected.to include('This is email 1 of 4 in the Team series') }
end
context 'not on gitlab.com' do
let(:is_gitlab_com) { false }
it { is_expected.to include('This is email 1 of 4 in the Team series', Gitlab::Routing.url_helpers.profile_notifications_url) }
end
end
end
end

View File

@ -23,6 +23,26 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Team do
expect(message.body_line2).to be_present
expect(message.cta_text).to be_present
end
describe '#progress' do
subject { message.progress }
before do
allow(Gitlab).to receive(:com?).and_return(is_gitlab_com)
end
context 'on gitlab.com' do
let(:is_gitlab_com) { true }
it { is_expected.to include("This is email #{series + 2} of 4 in the Team series") }
end
context 'not on gitlab.com' do
let(:is_gitlab_com) { false }
it { is_expected.to include("This is email #{series + 2} of 4 in the Team series", Gitlab::Routing.url_helpers.profile_notifications_url) }
end
end
end
context 'with series 2' do
@ -37,6 +57,26 @@ RSpec.describe Gitlab::Email::Message::InProductMarketing::Team do
expect(message.body_line2).to be_present
expect(message.cta_text).to be_present
end
describe '#progress' do
subject { message.progress }
before do
allow(Gitlab).to receive(:com?).and_return(is_gitlab_com)
end
context 'on gitlab.com' do
let(:is_gitlab_com) { true }
it { is_expected.to include('This is email 4 of 4 in the Team series') }
end
context 'not on gitlab.com' do
let(:is_gitlab_com) { false }
it { is_expected.to include('This is email 4 of 4 in the Team series', Gitlab::Routing.url_helpers.profile_notifications_url) }
end
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More