Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
575ccb036e
commit
143f196f8b
|
@ -2,10 +2,6 @@
|
|||
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||
entry.
|
||||
|
||||
## 12.4.3
|
||||
|
||||
- No changes.
|
||||
|
||||
## 12.4.2
|
||||
|
||||
### Fixed (10 changes)
|
||||
|
|
|
@ -168,7 +168,7 @@ export default {
|
|||
}
|
||||
|
||||
const recentBoardsPromise = new Promise((resolve, reject) =>
|
||||
gl.boardService
|
||||
boardsStore
|
||||
.recentBoards()
|
||||
.then(resolve)
|
||||
.catch(err => {
|
||||
|
@ -184,7 +184,7 @@ export default {
|
|||
}),
|
||||
);
|
||||
|
||||
Promise.all([gl.boardService.allBoards(), recentBoardsPromise])
|
||||
Promise.all([boardsStore.allBoards(), recentBoardsPromise])
|
||||
.then(([allBoards, recentBoards]) => [allBoards.data, recentBoards.data])
|
||||
.then(([allBoardsJson, recentBoardsJson]) => {
|
||||
this.loading = false;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* eslint-disable func-names, no-var, one-var, no-else-return */
|
||||
/* eslint-disable func-names, no-else-return */
|
||||
|
||||
import $ from 'jquery';
|
||||
import Api from './api';
|
||||
|
@ -7,7 +7,7 @@ import { s__ } from './locale';
|
|||
|
||||
const projectSelect = () => {
|
||||
$('.ajax-project-select').each(function(i, select) {
|
||||
var placeholder;
|
||||
let placeholder;
|
||||
const simpleFilter = $(select).data('simpleFilter') || false;
|
||||
const isInstantiated = $(select).data('select2');
|
||||
this.groupId = $(select).data('groupId');
|
||||
|
@ -31,20 +31,17 @@ const projectSelect = () => {
|
|||
placeholder,
|
||||
minimumInputLength: 0,
|
||||
query: query => {
|
||||
var finalCallback, projectsCallback;
|
||||
finalCallback = function(projects) {
|
||||
var data;
|
||||
data = {
|
||||
let projectsCallback;
|
||||
const finalCallback = function(projects) {
|
||||
const data = {
|
||||
results: projects,
|
||||
};
|
||||
return query.callback(data);
|
||||
};
|
||||
if (this.includeGroups) {
|
||||
projectsCallback = function(projects) {
|
||||
var groupsCallback;
|
||||
groupsCallback = function(groups) {
|
||||
var data;
|
||||
data = groups.concat(projects);
|
||||
const groupsCallback = function(groups) {
|
||||
const data = groups.concat(projects);
|
||||
return finalCallback(data);
|
||||
};
|
||||
return Api.groups(query.term, {}, groupsCallback);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import { GlTooltipDirective, GlLink, GlButton, GlLoadingIcon } from '@gitlab/ui';
|
||||
import defaultAvatarUrl from 'images/no_avatar.png';
|
||||
import { sprintf, s__ } from '~/locale';
|
||||
import Icon from '../../vue_shared/components/icon.vue';
|
||||
import UserAvatarLink from '../../vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
|
@ -83,6 +84,7 @@ export default {
|
|||
this.showDescription = !this.showDescription;
|
||||
},
|
||||
},
|
||||
defaultAvatarUrl,
|
||||
};
|
||||
</script>
|
||||
|
||||
|
@ -97,6 +99,9 @@ export default {
|
|||
:img-size="40"
|
||||
class="avatar-cell"
|
||||
/>
|
||||
<span v-else class="avatar-cell user-avatar-link">
|
||||
<img :src="$options.defaultAvatarUrl" width="40" height="40" class="avatar s40" />
|
||||
</span>
|
||||
<div class="commit-detail flex-list">
|
||||
<div class="commit-content qa-commit-content">
|
||||
<gl-link :href="commit.webUrl" class="commit-row-message item-title">
|
||||
|
@ -119,6 +124,9 @@ export default {
|
|||
>
|
||||
{{ commit.author.name }}
|
||||
</gl-link>
|
||||
<template v-else>
|
||||
{{ commit.authorName }}
|
||||
</template>
|
||||
{{ s__('LastCommit|authored') }}
|
||||
<timeago-tooltip :time="commit.authoredDate" tooltip-placement="bottom" />
|
||||
</div>
|
||||
|
@ -132,9 +140,8 @@ export default {
|
|||
</div>
|
||||
<div class="commit-actions flex-row">
|
||||
<div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div>
|
||||
<div class="ci-status-link">
|
||||
<div v-if="commit.pipeline" class="ci-status-link">
|
||||
<gl-link
|
||||
v-if="commit.pipeline"
|
||||
v-gl-tooltip.left
|
||||
:href="commit.pipeline.detailedStatus.detailsPath"
|
||||
:title="statusTitle"
|
||||
|
|
|
@ -75,6 +75,7 @@ export default {
|
|||
v-for="entry in val"
|
||||
:id="entry.id"
|
||||
:key="`${entry.flatPath}-${entry.id}`"
|
||||
:sha="entry.sha"
|
||||
:project-path="projectPath"
|
||||
:current-path="path"
|
||||
:name="entry.name"
|
||||
|
|
|
@ -37,6 +37,10 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
sha: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
projectPath: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
@ -98,7 +102,7 @@ export default {
|
|||
return this.path.replace(new RegExp(`^${this.currentPath}/`), '');
|
||||
},
|
||||
shortSha() {
|
||||
return this.id.slice(0, 8);
|
||||
return this.sha.slice(0, 8);
|
||||
},
|
||||
hasLockLabel() {
|
||||
return this.commit && this.commit.lockLabel;
|
||||
|
|
|
@ -26,9 +26,12 @@ export function fetchLogsTree(client, path, offset, resolver = null) {
|
|||
const { ref } = client.readQuery({ query: getRef });
|
||||
|
||||
fetchpromise = axios
|
||||
.get(`${gon.gitlab_url}/${projectPath}/refs/${ref}/logs_tree/${path.replace(/^\//, '')}`, {
|
||||
params: { format: 'json', offset },
|
||||
})
|
||||
.get(
|
||||
`${gon.relative_url_root}/${projectPath}/refs/${ref}/logs_tree/${path.replace(/^\//, '')}`,
|
||||
{
|
||||
params: { format: 'json', offset },
|
||||
},
|
||||
)
|
||||
.then(({ data, headers }) => {
|
||||
const headerLogsOffset = headers['more-logs-offset'];
|
||||
const { commits } = client.readQuery({ query: getCommits });
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
fragment TreeEntry on Entry {
|
||||
id
|
||||
sha
|
||||
name
|
||||
flatPath
|
||||
type
|
||||
|
|
|
@ -8,6 +8,7 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
|
|||
description
|
||||
webUrl
|
||||
authoredDate
|
||||
authorName
|
||||
author {
|
||||
name
|
||||
avatarUrl
|
||||
|
|
|
@ -218,7 +218,7 @@ export default {
|
|||
display: inline-block;
|
||||
flex: 1;
|
||||
max-width: inherit;
|
||||
height: 18px;
|
||||
height: 19px;
|
||||
line-height: 16px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
|
|
@ -357,12 +357,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
.filter-dropdown-container > div {
|
||||
margin: 0;
|
||||
.filter-dropdown-container {
|
||||
> div {
|
||||
margin: 0;
|
||||
|
||||
> .btn {
|
||||
margin: 0 0 10px;
|
||||
width: 100%;
|
||||
> .btn {
|
||||
margin: 0 0 10px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.board-labels-toggle-wrapper {
|
||||
margin-bottom: $gl-input-padding;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -497,7 +497,7 @@
|
|||
.add-issues-footer-to-list {
|
||||
padding-left: $gl-vert-padding;
|
||||
padding-right: $gl-vert-padding;
|
||||
line-height: 34px;
|
||||
line-height: $input-height;
|
||||
}
|
||||
|
||||
.issue-card-selected {
|
||||
|
@ -551,9 +551,5 @@
|
|||
* Make the wrapper the same height as a button so it aligns properly when the
|
||||
* filtered-search-box input element increases in size on Linux smaller breakpoints
|
||||
*/
|
||||
height: 34px;
|
||||
|
||||
@include media-breakpoint-down(sm) {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
height: $input-height;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,8 @@ module Types
|
|||
description: 'Web URL of the commit'
|
||||
field :signature_html, type: GraphQL::STRING_TYPE, null: true, calls_gitaly: true,
|
||||
description: 'Rendered HTML of the commit signature'
|
||||
field :author_name, type: GraphQL::STRING_TYPE, null: true,
|
||||
description: 'Commit authors name'
|
||||
|
||||
# models/commit lazy loads the author by email
|
||||
field :author, type: Types::UserType, null: true,
|
||||
|
|
|
@ -8,6 +8,7 @@ module Types
|
|||
|
||||
value 'DUE_DATE_ASC', 'Due date by ascending order', value: 'due_date_asc'
|
||||
value 'DUE_DATE_DESC', 'Due date by descending order', value: 'due_date_desc'
|
||||
value 'RELATIVE_POSITION_ASC', 'Relative position by ascending order', value: 'relative_position_asc'
|
||||
end
|
||||
# rubocop: enable Graphql/AuthorizeTypes
|
||||
end
|
||||
|
|
|
@ -5,6 +5,7 @@ module Types
|
|||
include Types::BaseInterface
|
||||
|
||||
field :id, GraphQL::ID_TYPE, null: false # rubocop:disable Graphql/Descriptions
|
||||
field :sha, GraphQL::STRING_TYPE, null: false, description: "Last commit sha for entry", method: :id
|
||||
field :name, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
|
||||
field :type, Tree::TypeEnum, null: false # rubocop:disable Graphql/Descriptions
|
||||
field :path, GraphQL::STRING_TYPE, null: false # rubocop:disable Graphql/Descriptions
|
||||
|
|
|
@ -898,12 +898,6 @@ module Ci
|
|||
value.with_indifferent_access
|
||||
end
|
||||
end
|
||||
|
||||
def build_attributes_from_config
|
||||
return {} unless pipeline.config_processor
|
||||
|
||||
pipeline.config_processor.build_attributes(name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -551,23 +551,6 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def stage_seeds
|
||||
return [] unless config_processor
|
||||
|
||||
strong_memoize(:stage_seeds) do
|
||||
seeds = config_processor.stages_attributes.inject([]) do |previous_stages, attributes|
|
||||
seed = Gitlab::Ci::Pipeline::Seed::Stage.new(self, attributes, previous_stages)
|
||||
previous_stages + [seed]
|
||||
end
|
||||
|
||||
seeds.select(&:included?)
|
||||
end
|
||||
end
|
||||
|
||||
def seeds_size
|
||||
stage_seeds.sum(&:size)
|
||||
end
|
||||
|
||||
def has_kubernetes_active?
|
||||
project.deployment_platform&.active?
|
||||
end
|
||||
|
@ -587,62 +570,14 @@ module Ci
|
|||
end
|
||||
end
|
||||
|
||||
def set_config_source
|
||||
if ci_yaml_from_repo
|
||||
self.config_source = :repository_source
|
||||
elsif implied_ci_yaml_file
|
||||
self.config_source = :auto_devops_source
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# TODO, setting yaml_errors should be moved to the pipeline creation chain.
|
||||
#
|
||||
def config_processor
|
||||
return unless ci_yaml_file
|
||||
return @config_processor if defined?(@config_processor)
|
||||
|
||||
@config_processor ||= begin
|
||||
::Gitlab::Ci::YamlProcessor.new(ci_yaml_file, { project: project, sha: sha, user: user })
|
||||
rescue Gitlab::Ci::YamlProcessor::ValidationError => e
|
||||
self.yaml_errors = e.message
|
||||
nil
|
||||
rescue => ex
|
||||
self.yaml_errors = "Undefined error (#{Labkit::Correlation::CorrelationId.current_id})"
|
||||
|
||||
Gitlab::Sentry.track_acceptable_exception(ex, extra: {
|
||||
project_id: project.id,
|
||||
sha: sha,
|
||||
ci_yaml_file: ci_yaml_file_path
|
||||
})
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def ci_yaml_file_path
|
||||
# TODO: this logic is duplicate with Pipeline::Chain::Config::Content
|
||||
# we should persist this is `ci_pipelines.config_path`
|
||||
def config_path
|
||||
return unless repository_source? || unknown_source?
|
||||
|
||||
project.ci_config_path.presence || '.gitlab-ci.yml'
|
||||
end
|
||||
|
||||
def ci_yaml_file
|
||||
return @ci_yaml_file if defined?(@ci_yaml_file)
|
||||
|
||||
@ci_yaml_file =
|
||||
if auto_devops_source?
|
||||
implied_ci_yaml_file
|
||||
else
|
||||
ci_yaml_from_repo
|
||||
end
|
||||
|
||||
if @ci_yaml_file
|
||||
@ci_yaml_file
|
||||
else
|
||||
self.yaml_errors = "Failed to load CI/CD config file for #{sha}"
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def has_yaml_errors?
|
||||
yaml_errors.present?
|
||||
end
|
||||
|
@ -711,7 +646,7 @@ module Ci
|
|||
def predefined_variables
|
||||
Gitlab::Ci::Variables::Collection.new.tap do |variables|
|
||||
variables.append(key: 'CI_PIPELINE_IID', value: iid.to_s)
|
||||
variables.append(key: 'CI_CONFIG_PATH', value: ci_yaml_file_path)
|
||||
variables.append(key: 'CI_CONFIG_PATH', value: config_path)
|
||||
variables.append(key: 'CI_PIPELINE_SOURCE', value: source.to_s)
|
||||
variables.append(key: 'CI_COMMIT_MESSAGE', value: git_commit_message.to_s)
|
||||
variables.append(key: 'CI_COMMIT_TITLE', value: git_commit_full_title.to_s)
|
||||
|
@ -906,24 +841,6 @@ module Ci
|
|||
|
||||
private
|
||||
|
||||
def ci_yaml_from_repo
|
||||
return unless project
|
||||
return unless sha
|
||||
return unless ci_yaml_file_path
|
||||
|
||||
project.repository.gitlab_ci_yml_for(sha, ci_yaml_file_path)
|
||||
rescue GRPC::NotFound, GRPC::Internal
|
||||
nil
|
||||
end
|
||||
|
||||
def implied_ci_yaml_file
|
||||
return unless project
|
||||
|
||||
if project.auto_devops_enabled?
|
||||
Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content
|
||||
end
|
||||
end
|
||||
|
||||
def pipeline_data
|
||||
Gitlab::DataBuilder::Pipeline.build(self)
|
||||
end
|
||||
|
|
|
@ -57,18 +57,20 @@ module Storage
|
|||
# Move the namespace directory in all storages used by member projects
|
||||
repository_storages(legacy_only: true).each do |repository_storage|
|
||||
# Ensure old directory exists before moving it
|
||||
gitlab_shell.add_namespace(repository_storage, full_path_before_last_save)
|
||||
Gitlab::GitalyClient::NamespaceService.allow do
|
||||
gitlab_shell.add_namespace(repository_storage, full_path_before_last_save)
|
||||
|
||||
# Ensure new directory exists before moving it (if there's a parent)
|
||||
gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent
|
||||
# Ensure new directory exists before moving it (if there's a parent)
|
||||
gitlab_shell.add_namespace(repository_storage, parent.full_path) if parent
|
||||
|
||||
unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path)
|
||||
unless gitlab_shell.mv_namespace(repository_storage, full_path_before_last_save, full_path)
|
||||
|
||||
Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" # rubocop:disable Gitlab/RailsLogger
|
||||
Rails.logger.error "Exception moving path #{repository_storage} from #{full_path_before_last_save} to #{full_path}" # rubocop:disable Gitlab/RailsLogger
|
||||
|
||||
# if we cannot move namespace directory we should rollback
|
||||
# db changes in order to prevent out of sync between db and fs
|
||||
raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
|
||||
# if we cannot move namespace directory we should rollback
|
||||
# db changes in order to prevent out of sync between db and fs
|
||||
raise Gitlab::UpdatePathError.new('namespace directory cannot be moved')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -95,13 +97,15 @@ module Storage
|
|||
# We will remove it later async
|
||||
new_path = "#{full_path}+#{id}+deleted"
|
||||
|
||||
if gitlab_shell.mv_namespace(repository_storage, full_path, new_path)
|
||||
Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}")
|
||||
Gitlab::GitalyClient::NamespaceService.allow do
|
||||
if gitlab_shell.mv_namespace(repository_storage, full_path, new_path)
|
||||
Gitlab::AppLogger.info %Q(Namespace directory "#{full_path}" moved to "#{new_path}")
|
||||
|
||||
# Remove namespace directory async with delay so
|
||||
# GitLab has time to remove all projects first
|
||||
run_after_commit do
|
||||
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path)
|
||||
# Remove namespace directory async with delay so
|
||||
# GitLab has time to remove all projects first
|
||||
run_after_commit do
|
||||
GitlabShellWorker.perform_in(5.minutes, :rm_namespace, repository_storage, new_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ProjectCiCdSetting < ApplicationRecord
|
||||
# TODO: remove once GitLab 12.7 is released
|
||||
# https://gitlab.com/gitlab-org/gitlab/issues/36651
|
||||
self.ignored_columns += %i[merge_trains_enabled]
|
||||
belongs_to :project, inverse_of: :ci_cd_settings
|
||||
|
||||
# The version of the schema that first introduced this model/table.
|
||||
|
|
|
@ -21,7 +21,6 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
|
||||
def statistics_anchors(show_auto_devops_callout:)
|
||||
[
|
||||
license_anchor_data,
|
||||
commits_anchor_data,
|
||||
branches_anchor_data,
|
||||
tags_anchor_data,
|
||||
|
@ -32,6 +31,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
def statistics_buttons(show_auto_devops_callout:)
|
||||
[
|
||||
readme_anchor_data,
|
||||
license_anchor_data,
|
||||
changelog_anchor_data,
|
||||
contribution_guide_anchor_data,
|
||||
autodevops_anchor_data(show_auto_devops_callout: show_auto_devops_callout),
|
||||
|
@ -41,15 +41,14 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
end
|
||||
|
||||
def empty_repo_statistics_anchors
|
||||
[
|
||||
license_anchor_data
|
||||
].compact.select { |item| item.is_link }
|
||||
[]
|
||||
end
|
||||
|
||||
def empty_repo_statistics_buttons
|
||||
[
|
||||
new_file_anchor_data,
|
||||
readme_anchor_data,
|
||||
license_anchor_data,
|
||||
changelog_anchor_data,
|
||||
contribution_guide_anchor_data,
|
||||
gitlab_ci_anchor_data
|
||||
|
@ -227,17 +226,18 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
|
|||
icon = statistic_icon('scale')
|
||||
|
||||
if repository.license_blob.present?
|
||||
AnchorData.new(true,
|
||||
icon + content_tag(:strong, license_short_name, class: 'project-stat-value'),
|
||||
license_path)
|
||||
AnchorData.new(false,
|
||||
icon + content_tag(:span, license_short_name, class: 'project-stat-value'),
|
||||
license_path,
|
||||
'default')
|
||||
else
|
||||
if current_user && can_current_user_push_to_default_branch?
|
||||
AnchorData.new(true,
|
||||
content_tag(:span, icon + _('Add license'), class: 'add-license-link d-flex'),
|
||||
AnchorData.new(false,
|
||||
content_tag(:span, statistic_icon + _('Add LICENSE'), class: 'add-license-link d-flex'),
|
||||
add_license_path)
|
||||
else
|
||||
AnchorData.new(true,
|
||||
icon + content_tag(:strong, _('No license. All rights reserved'), class: 'project-stat-value'),
|
||||
AnchorData.new(false,
|
||||
icon + content_tag(:span, _('No license. All rights reserved'), class: 'project-stat-value'),
|
||||
nil)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,12 +7,14 @@ module Ci
|
|||
CreateError = Class.new(StandardError)
|
||||
|
||||
SEQUENCE = [Gitlab::Ci::Pipeline::Chain::Build,
|
||||
Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
|
||||
Gitlab::Ci::Pipeline::Chain::Validate::Abilities,
|
||||
Gitlab::Ci::Pipeline::Chain::Validate::Repository,
|
||||
Gitlab::Ci::Pipeline::Chain::Validate::Config,
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Content,
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Process,
|
||||
Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
|
||||
Gitlab::Ci::Pipeline::Chain::Skip,
|
||||
Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
|
||||
Gitlab::Ci::Pipeline::Chain::Seed,
|
||||
Gitlab::Ci::Pipeline::Chain::Limit::Size,
|
||||
Gitlab::Ci::Pipeline::Chain::Populate,
|
||||
Gitlab::Ci::Pipeline::Chain::Create,
|
||||
|
|
|
@ -39,10 +39,6 @@
|
|||
%th
|
||||
= render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
|
||||
|
||||
- elsif pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
|
||||
.bs-callout.bs-callout-warning
|
||||
= _("%{gitlab_ci_yml} not found in this commit") % { gitlab_ci_yml: ".gitlab-ci.yml" }
|
||||
|
||||
- if @pipeline.failed_builds.present?
|
||||
#js-tab-failures.build-failures.tab-pane.build-page
|
||||
%table.table.responsive-table.ci-table.responsive-table-sm-rounded
|
||||
|
|
|
@ -5,169 +5,170 @@
|
|||
- user_can_admin_list = board && can?(current_user, :admin_list, board.resource_parent)
|
||||
|
||||
.issues-filters{ class: ("w-100" if type == :boards_modal) }
|
||||
.issues-details-filters.filtered-search-block.d-flex.flex-column.flex-md-row{ class: block_css_class, "v-pre" => type == :boards_modal }
|
||||
- if type == :boards
|
||||
= render "shared/boards/switcher", board: board
|
||||
= form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do
|
||||
- if params[:search].present?
|
||||
= hidden_field_tag :search, params[:search]
|
||||
- if @can_bulk_update
|
||||
.check-all-holder.d-none.d-sm-block.hidden
|
||||
= check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
|
||||
.issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
|
||||
.filtered-search-box
|
||||
- if type != :boards_modal && type != :boards
|
||||
= dropdown_tag(_('Recent searches'),
|
||||
options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
|
||||
toggle_class: "filtered-search-history-dropdown-toggle-button",
|
||||
dropdown_class: "filtered-search-history-dropdown",
|
||||
content_class: "filtered-search-history-dropdown-content" }) do
|
||||
.js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
|
||||
.filtered-search-box-input-container.droplab-dropdown
|
||||
.scroll-container
|
||||
%ul.tokens-container.list-unstyled
|
||||
%li.input-token
|
||||
%input.form-control.filtered-search{ search_filter_input_options(type) }
|
||||
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { action: 'submit' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= sprite_icon('search')
|
||||
%span
|
||||
= _('Press Enter or click to search')
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
|
||||
-# haml lint's ClassAttributeWithStaticValue
|
||||
%svg
|
||||
%use{ 'xlink:href': "#{'{{icon}}'}" }
|
||||
%span.js-filter-hint
|
||||
{{hint}}
|
||||
%span.js-filter-tag.dropdown-light-content
|
||||
{{tag}}
|
||||
#js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
- if current_user
|
||||
.issues-details-filters.filtered-search-block.d-flex.flex-column.flex-lg-row{ class: block_css_class, "v-pre" => type == :boards_modal }
|
||||
.d-flex.flex-column.flex-md-row.flex-grow-1.mb-lg-0.mb-md-2.mb-sm-0
|
||||
- if type == :boards
|
||||
= render "shared/boards/switcher", board: board
|
||||
= form_tag page_filter_path, method: :get, class: 'filter-form js-filter-form w-100' do
|
||||
- if params[:search].present?
|
||||
= hidden_field_tag :search, params[:search]
|
||||
- if @can_bulk_update
|
||||
.check-all-holder.d-none.d-sm-block.hidden
|
||||
= check_box_tag "check-all-issues", nil, false, class: "check-all-issues left"
|
||||
.issues-other-filters.filtered-search-wrapper.d-flex.flex-column.flex-md-row
|
||||
.filtered-search-box
|
||||
- if type != :boards_modal && type != :boards
|
||||
= dropdown_tag(_('Recent searches'),
|
||||
options: { wrapper_class: "filtered-search-history-dropdown-wrapper",
|
||||
toggle_class: "filtered-search-history-dropdown-toggle-button",
|
||||
dropdown_class: "filtered-search-history-dropdown",
|
||||
content_class: "filtered-search-history-dropdown-content" }) do
|
||||
.js-filtered-search-history-dropdown{ data: { full_path: search_history_storage_prefix } }
|
||||
.filtered-search-box-input-container.droplab-dropdown
|
||||
.scroll-container
|
||||
%ul.tokens-container.list-unstyled
|
||||
%li.input-token
|
||||
%input.form-control.filtered-search{ search_filter_input_options(type) }
|
||||
#js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown
|
||||
%ul{ data: { dropdown: true } }
|
||||
= render 'shared/issuable/user_dropdown_item',
|
||||
user: current_user
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
= render 'shared/issuable/user_dropdown_item',
|
||||
user: User.new(username: '{{username}}', name: '{{name}}'),
|
||||
avatar: { lazy: true, url: '{{avatar_url}}' }
|
||||
#js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('None')
|
||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Any')
|
||||
%li.divider.droplab-item-ignore
|
||||
%li.filter-dropdown-item{ data: { action: 'submit' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= sprite_icon('search')
|
||||
%span
|
||||
= _('Press Enter or click to search')
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
-# Encapsulate static class name `{{icon}}` inside #{} to bypass
|
||||
-# haml lint's ClassAttributeWithStaticValue
|
||||
%svg
|
||||
%use{ 'xlink:href': "#{'{{icon}}'}" }
|
||||
%span.js-filter-hint
|
||||
{{hint}}
|
||||
%span.js-filter-tag.dropdown-light-content
|
||||
{{tag}}
|
||||
#js-dropdown-author.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
- if current_user
|
||||
%ul{ data: { dropdown: true } }
|
||||
= render 'shared/issuable/user_dropdown_item',
|
||||
user: current_user
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
= render 'shared/issuable/user_dropdown_item',
|
||||
user: current_user
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
= render 'shared/issuable/user_dropdown_item',
|
||||
user: User.new(username: '{{username}}', name: '{{name}}'),
|
||||
avatar: { lazy: true, url: '{{avatar_url}}' }
|
||||
= render_if_exists 'shared/issuable/approver_dropdown'
|
||||
#js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('None')
|
||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Any')
|
||||
%li.filter-dropdown-item{ data: { value: 'Upcoming' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Upcoming')
|
||||
%li.filter-dropdown-item{ data: { value: 'Started' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Started')
|
||||
%li.divider.droplab-item-ignore
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link.js-data-value{ type: 'button' }
|
||||
{{title}}
|
||||
#js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('None')
|
||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Any')
|
||||
%li.divider.droplab-item-ignore
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link.js-data-value{ type: 'button' }
|
||||
{{title}}
|
||||
#js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('None')
|
||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Any')
|
||||
%li.divider.droplab-item-ignore
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
%span.dropdown-label-box{ style: 'background: {{color}}' }
|
||||
%span.label-title.js-data-value
|
||||
user: User.new(username: '{{username}}', name: '{{name}}'),
|
||||
avatar: { lazy: true, url: '{{avatar_url}}' }
|
||||
#js-dropdown-assignee.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('None')
|
||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Any')
|
||||
%li.divider.droplab-item-ignore
|
||||
- if current_user
|
||||
= render 'shared/issuable/user_dropdown_item',
|
||||
user: current_user
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
= render 'shared/issuable/user_dropdown_item',
|
||||
user: User.new(username: '{{username}}', name: '{{name}}'),
|
||||
avatar: { lazy: true, url: '{{avatar_url}}' }
|
||||
= render_if_exists 'shared/issuable/approver_dropdown'
|
||||
#js-dropdown-milestone.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('None')
|
||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Any')
|
||||
%li.filter-dropdown-item{ data: { value: 'Upcoming' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Upcoming')
|
||||
%li.filter-dropdown-item{ data: { value: 'Started' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Started')
|
||||
%li.divider.droplab-item-ignore
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link.js-data-value{ type: 'button' }
|
||||
{{title}}
|
||||
#js-dropdown-release.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('None')
|
||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Any')
|
||||
%li.divider.droplab-item-ignore
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link.js-data-value{ type: 'button' }
|
||||
{{title}}
|
||||
#js-dropdown-label.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('None')
|
||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Any')
|
||||
%li.divider.droplab-item-ignore
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
%span.dropdown-label-box{ style: 'background: {{color}}' }
|
||||
%span.label-title.js-data-value
|
||||
{{title}}
|
||||
#js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('None')
|
||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Any')
|
||||
%li.divider.droplab-item-ignore
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
%gl-emoji
|
||||
%span.js-data-value.prepend-left-10
|
||||
{{name}}
|
||||
#js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul.filter-dropdown{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Yes')
|
||||
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('No')
|
||||
#js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul.filter-dropdown{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Yes')
|
||||
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('No')
|
||||
#js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link.js-data-value.monospace
|
||||
{{title}}
|
||||
#js-dropdown-my-reaction.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'None' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('None')
|
||||
%li.filter-dropdown-item{ data: { value: 'Any' } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Any')
|
||||
%li.divider.droplab-item-ignore
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
%gl-emoji
|
||||
%span.js-data-value.prepend-left-10
|
||||
{{name}}
|
||||
#js-dropdown-wip.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul.filter-dropdown{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Yes')
|
||||
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('No')
|
||||
#js-dropdown-confidential.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul.filter-dropdown{ data: { dropdown: true } }
|
||||
%li.filter-dropdown-item{ data: { value: 'yes', capitalize: true } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('Yes')
|
||||
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
|
||||
%button.btn.btn-link{ type: 'button' }
|
||||
= _('No')
|
||||
#js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
|
||||
%ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
|
||||
%li.filter-dropdown-item
|
||||
%button.btn.btn-link.js-data-value.monospace
|
||||
{{title}}
|
||||
|
||||
= render_if_exists 'shared/issuable/filter_weight', type: type
|
||||
= render_if_exists 'shared/issuable/filter_weight', type: type
|
||||
|
||||
%button.clear-search.hidden{ type: 'button' }
|
||||
= icon('times')
|
||||
#js-board-labels-toggle
|
||||
.filter-dropdown-container.d-flex.flex-column.flex-md-row
|
||||
- if type == :boards
|
||||
.js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
|
||||
- if user_can_admin_list
|
||||
= render 'shared/issuable/board_create_list_dropdown', board: board
|
||||
- if @project
|
||||
#js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
|
||||
#js-toggle-focus-btn
|
||||
- elsif is_not_boards_modal_or_productivity_analytics
|
||||
= render 'shared/issuable/sort_dropdown'
|
||||
%button.clear-search.hidden{ type: 'button' }
|
||||
= icon('times')
|
||||
.filter-dropdown-container.d-flex.flex-column.flex-md-row
|
||||
#js-board-labels-toggle
|
||||
- if type == :boards
|
||||
.js-board-config{ data: { can_admin_list: user_can_admin_list, has_scope: board.scoped? } }
|
||||
- if user_can_admin_list
|
||||
= render 'shared/issuable/board_create_list_dropdown', board: board
|
||||
- if @project
|
||||
#js-add-issues-btn.prepend-left-10{ data: { can_admin_list: can?(current_user, :admin_list, @project) } }
|
||||
#js-toggle-focus-btn
|
||||
- elsif is_not_boards_modal_or_productivity_analytics
|
||||
= render 'shared/issuable/sort_dropdown'
|
||||
|
|
|
@ -8,6 +8,8 @@ class GitlabShellWorker
|
|||
latency_sensitive_worker!
|
||||
|
||||
def perform(action, *arg)
|
||||
gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
|
||||
Gitlab::GitalyClient::NamespaceService.allow do
|
||||
gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Move add license button to project buttons
|
||||
merge_request: 19370
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: 'Graphql query for issues can now be sorted by relative_position'
|
||||
merge_request: 19713
|
||||
author:
|
||||
type: added
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: remove all references of BoardService in boards_selector.vue
|
||||
merge_request: 20147
|
||||
author: nuwe1
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Remove var from project_select.js
|
||||
merge_request: 20091
|
||||
author: Lee Tickett
|
||||
type: other
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Add backtrace to production_json.log
|
||||
merge_request: 20122
|
||||
author:
|
||||
type: changed
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
title: Support template_project_id parameter in project creation API
|
||||
merge_request: 20258
|
||||
author:
|
||||
type: added
|
|
@ -10,6 +10,11 @@ unless Sidekiq.server?
|
|||
# unmaintained gem that monkey patches `Time`
|
||||
config.lograge.formatter = Lograge::Formatters::Json.new
|
||||
config.lograge.logger = ActiveSupport::Logger.new(filename)
|
||||
config.lograge.before_format = lambda do |data, payload|
|
||||
data.delete(:error)
|
||||
data
|
||||
end
|
||||
|
||||
# Add request parameters to log output
|
||||
config.lograge.custom_options = lambda do |event|
|
||||
params = event.payload[:params]
|
||||
|
@ -36,6 +41,20 @@ unless Sidekiq.server?
|
|||
payload[:cpu_s] = cpu_s
|
||||
end
|
||||
|
||||
# https://github.com/roidrage/lograge#logging-errors--exceptions
|
||||
exception = event.payload[:exception_object]
|
||||
|
||||
if exception
|
||||
payload[:exception] = {
|
||||
class: exception.class.name,
|
||||
message: exception.message
|
||||
}
|
||||
|
||||
if exception.backtrace
|
||||
payload[:exception][:backtrace] = Gitlab::Profiler.clean_backtrace(exception.backtrace)
|
||||
end
|
||||
end
|
||||
|
||||
payload
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,6 +42,48 @@ User clone/fetch activity using http transport appears in this log as `action: g
|
|||
In addition, the log contains the IP address from which the request originated
|
||||
(`remote_ip`) as well as the user's ID (`user_id`), and username (`username`).
|
||||
|
||||
NOTE: **Note:** Starting with GitLab 12.5, if an error occurs, an
|
||||
`exception` field is included with `class`, `message`, and
|
||||
`backtrace`. Previous versions included an `error` field instead of
|
||||
`exception.class` and `exception.message`. For example:
|
||||
|
||||
```json
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/admin",
|
||||
"format": "html",
|
||||
"controller": "Admin::DashboardController",
|
||||
"action": "index",
|
||||
"status": 500,
|
||||
"duration": 2584.11,
|
||||
"view": 0,
|
||||
"db": 9.21,
|
||||
"time": "2019-11-14T13:12:46.156Z",
|
||||
"params": [],
|
||||
"remote_ip": "127.0.0.1",
|
||||
"user_id": 1,
|
||||
"username": "root",
|
||||
"ua": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:70.0) Gecko/20100101 Firefox/70.0",
|
||||
"queue_duration": 274.35,
|
||||
"correlation_id": "KjDVUhNvvV3",
|
||||
"cpu_s": 2.837645135999999,
|
||||
"exception": {
|
||||
"class": "NameError",
|
||||
"message": "undefined local variable or method `adsf' for #<Admin::DashboardController:0x00007ff3c9648588>",
|
||||
"backtrace": [
|
||||
"app/controllers/admin/dashboard_controller.rb:11:in `index'",
|
||||
"ee/app/controllers/ee/admin/dashboard_controller.rb:14:in `index'",
|
||||
"ee/lib/gitlab/ip_address_state.rb:10:in `with'",
|
||||
"ee/app/controllers/ee/application_controller.rb:43:in `set_current_ip_address'",
|
||||
"lib/gitlab/session.rb:11:in `with_session'",
|
||||
"app/controllers/application_controller.rb:450:in `set_session_storage'",
|
||||
"app/controllers/application_controller.rb:444:in `set_locale'",
|
||||
"ee/lib/gitlab/jira/middleware.rb:19:in `call'"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## `production.log`
|
||||
|
||||
This file lives in `/var/log/gitlab/gitlab-rails/production.log` for
|
||||
|
|
|
@ -76,6 +76,11 @@ type Blob implements Entry {
|
|||
lfsOid: String
|
||||
name: String!
|
||||
path: String!
|
||||
|
||||
"""
|
||||
Last commit sha for entry
|
||||
"""
|
||||
sha: String!
|
||||
type: EntryType!
|
||||
webUrl: String
|
||||
}
|
||||
|
@ -121,6 +126,11 @@ type Commit {
|
|||
"""
|
||||
author: User
|
||||
|
||||
"""
|
||||
Commit authors name
|
||||
"""
|
||||
authorName: String
|
||||
|
||||
"""
|
||||
Timestamp of when the commit was authored
|
||||
"""
|
||||
|
@ -1100,6 +1110,11 @@ interface Entry {
|
|||
id: ID!
|
||||
name: String!
|
||||
path: String!
|
||||
|
||||
"""
|
||||
Last commit sha for entry
|
||||
"""
|
||||
sha: String!
|
||||
type: EntryType!
|
||||
}
|
||||
|
||||
|
@ -2517,6 +2532,11 @@ enum IssueSort {
|
|||
"""
|
||||
DUE_DATE_DESC
|
||||
|
||||
"""
|
||||
Relative position by ascending order
|
||||
"""
|
||||
RELATIVE_POSITION_ASC
|
||||
|
||||
"""
|
||||
Created at ascending order
|
||||
"""
|
||||
|
@ -4767,6 +4787,11 @@ type Submodule implements Entry {
|
|||
id: ID!
|
||||
name: String!
|
||||
path: String!
|
||||
|
||||
"""
|
||||
Last commit sha for entry
|
||||
"""
|
||||
sha: String!
|
||||
treeUrl: String
|
||||
type: EntryType!
|
||||
webUrl: String
|
||||
|
@ -5113,6 +5138,11 @@ type TreeEntry implements Entry {
|
|||
id: ID!
|
||||
name: String!
|
||||
path: String!
|
||||
|
||||
"""
|
||||
Last commit sha for entry
|
||||
"""
|
||||
sha: String!
|
||||
type: EntryType!
|
||||
webUrl: String
|
||||
}
|
||||
|
|
|
@ -10227,6 +10227,20 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "authorName",
|
||||
"description": "Commit authors name",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "authoredDate",
|
||||
"description": "Timestamp of when the commit was authored",
|
||||
|
@ -11332,6 +11346,24 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "sha",
|
||||
"description": "Last commit sha for entry",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": null,
|
||||
|
@ -11453,6 +11485,24 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "sha",
|
||||
"description": "Last commit sha for entry",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": null,
|
||||
|
@ -11711,6 +11761,24 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "sha",
|
||||
"description": "Last commit sha for entry",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "treeUrl",
|
||||
"description": null,
|
||||
|
@ -11972,6 +12040,24 @@
|
|||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "sha",
|
||||
"description": "Last commit sha for entry",
|
||||
"args": [
|
||||
|
||||
],
|
||||
"type": {
|
||||
"kind": "NON_NULL",
|
||||
"name": null,
|
||||
"ofType": {
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"ofType": null
|
||||
}
|
||||
},
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "type",
|
||||
"description": null,
|
||||
|
@ -13680,6 +13766,12 @@
|
|||
"description": "Due date by descending order",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
{
|
||||
"name": "RELATIVE_POSITION_ASC",
|
||||
"description": "Relative position by ascending order",
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
}
|
||||
],
|
||||
"possibleTypes": null
|
||||
|
|
|
@ -36,6 +36,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `id` | ID! | |
|
||||
| `sha` | String! | Last commit sha for entry |
|
||||
| `name` | String! | |
|
||||
| `type` | EntryType! | |
|
||||
| `path` | String! | |
|
||||
|
@ -55,6 +56,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| `authoredDate` | Time | Timestamp of when the commit was authored |
|
||||
| `webUrl` | String! | Web URL of the commit |
|
||||
| `signatureHtml` | String | Rendered HTML of the commit signature |
|
||||
| `authorName` | String | Commit authors name |
|
||||
| `author` | User | Author of the commit |
|
||||
| `latestPipeline` | Pipeline | Latest pipeline of the commit |
|
||||
|
||||
|
@ -738,6 +740,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `id` | ID! | |
|
||||
| `sha` | String! | Last commit sha for entry |
|
||||
| `name` | String! | |
|
||||
| `type` | EntryType! | |
|
||||
| `path` | String! | |
|
||||
|
@ -794,6 +797,7 @@ The API can be explored interactively using the [GraphiQL IDE](../index.md#graph
|
|||
| Name | Type | Description |
|
||||
| --- | ---- | ---------- |
|
||||
| `id` | ID! | |
|
||||
| `sha` | String! | Last commit sha for entry |
|
||||
| `name` | String! | |
|
||||
| `type` | EntryType! | |
|
||||
| `path` | String! | |
|
||||
|
|
|
@ -948,6 +948,7 @@ POST /projects
|
|||
| `mirror_trigger_builds` | boolean | no | **(STARTER)** Pull mirroring triggers builds |
|
||||
| `initialize_with_readme` | boolean | no | `false` by default |
|
||||
| `template_name` | string | no | When used without `use_custom_template`, name of a [built-in project template](../gitlab-basics/create-project.md#built-in-templates). When used with `use_custom_template`, name of a custom project template |
|
||||
| `template_project_id` | integer | no | **(PREMIUM)** When used with `use_custom_template`, project ID of a custom project template. This is preferable to using `template_name` since `template_name` may be ambiguous. |
|
||||
| `use_custom_template` | boolean | no | **(PREMIUM)** Use either custom [instance](../user/admin_area/custom_project_templates.md) or [group](../user/group/custom_project_templates.md) (with `group_with_project_templates_id`) project template |
|
||||
| `group_with_project_templates_id` | integer | no | **(PREMIUM)** For group-level custom templates, specifies ID of group from which all the custom project templates are sourced. Leave empty for instance-level templates. Requires `use_custom_template` to be true |
|
||||
|
||||
|
|
|
@ -19,6 +19,14 @@ To save duplicated clients getting created in different apps, we have a
|
|||
[default client][default-client] that should be used. This setups the
|
||||
Apollo client with the correct URL and also sets the CSRF headers.
|
||||
|
||||
Default client accepts two parameters: `resolvers` and `config`.
|
||||
|
||||
- `resolvers` parameter is created to accept an object of resolvers for [local state management](#local-state-with-apollo) queries and mutations
|
||||
- `config` parameter takes an object of configuration settings:
|
||||
- `cacheConfig` field accepts an optional object of settings to [customize Apollo cache](https://github.com/apollographql/apollo-client/tree/master/packages/apollo-cache-inmemory#configuration)
|
||||
- `baseUrl` allows us to pass a URL for GraphQL endpoint different from our main endpoint (i.e.`${gon.relative_url_root}/api/graphql`)
|
||||
- `assumeImmutableResults` (set to `false` by default) - this setting, when set to `true`, will assume that every single operation on updating Apollo Cache is immutable. It also sets `freezeResults` to `true`, so any attempt on mutating Apollo Cache will throw a console warning in development environment. Please ensure you're following the immutability pattern on cache update operations before setting this option to `true`.
|
||||
|
||||
## GraphQL Queries
|
||||
|
||||
To save query compilation at runtime, webpack can directly import `.graphql`
|
||||
|
|
|
@ -71,7 +71,8 @@ module API
|
|||
optional :namespace_id, type: Integer, desc: 'Namespace ID for the new project. Default to the user namespace.'
|
||||
optional :import_url, type: String, desc: 'URL from which the project is imported'
|
||||
optional :template_name, type: String, desc: "Name of template from which to create project"
|
||||
mutually_exclusive :import_url, :template_name
|
||||
optional :template_project_id, type: Integer, desc: "Project ID of template from which to create project"
|
||||
mutually_exclusive :import_url, :template_name, :template_project_id
|
||||
end
|
||||
|
||||
def load_projects
|
||||
|
|
|
@ -5,7 +5,7 @@ module Gitlab
|
|||
module Pipeline
|
||||
module Chain
|
||||
class Base
|
||||
attr_reader :pipeline, :command
|
||||
attr_reader :pipeline, :command, :config
|
||||
|
||||
delegate :project, :current_user, to: :command
|
||||
|
||||
|
|
|
@ -22,8 +22,6 @@ module Gitlab
|
|||
external_pull_request: @command.external_pull_request,
|
||||
variables_attributes: Array(@command.variables_attributes)
|
||||
)
|
||||
|
||||
@pipeline.set_config_source
|
||||
end
|
||||
|
||||
def break?
|
||||
|
|
|
@ -10,7 +10,9 @@ module Gitlab
|
|||
:trigger_request, :schedule, :merge_request, :external_pull_request,
|
||||
:ignore_skip_ci, :save_incompleted,
|
||||
:seeds_block, :variables_attributes, :push_options,
|
||||
:chat_data, :allow_mirror_update
|
||||
:chat_data, :allow_mirror_update,
|
||||
# These attributes are set by Chains during processing:
|
||||
:config_content, :config_processor, :stage_seeds
|
||||
) do
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
module Pipeline
|
||||
module Chain
|
||||
module Config
|
||||
class Content < Chain::Base
|
||||
include Chain::Helpers
|
||||
|
||||
def perform!
|
||||
return if @command.config_content
|
||||
|
||||
if content = content_from_repo
|
||||
@command.config_content = content
|
||||
@pipeline.config_source = :repository_source
|
||||
# TODO: we should persist ci_config_path
|
||||
# @pipeline.config_path = ci_config_path
|
||||
elsif content = content_from_auto_devops
|
||||
@command.config_content = content
|
||||
@pipeline.config_source = :auto_devops_source
|
||||
end
|
||||
|
||||
unless @command.config_content
|
||||
return error("Missing #{ci_config_path} file")
|
||||
end
|
||||
end
|
||||
|
||||
def break?
|
||||
@pipeline.errors.any? || @pipeline.persisted?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def content_from_repo
|
||||
return unless project
|
||||
return unless @pipeline.sha
|
||||
return unless ci_config_path
|
||||
|
||||
project.repository.gitlab_ci_yml_for(@pipeline.sha, ci_config_path)
|
||||
rescue GRPC::NotFound, GRPC::Internal
|
||||
nil
|
||||
end
|
||||
|
||||
def content_from_auto_devops
|
||||
return unless project&.auto_devops_enabled?
|
||||
|
||||
Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content
|
||||
end
|
||||
|
||||
def ci_config_path
|
||||
project.ci_config_path.presence || '.gitlab-ci.yml'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
module Pipeline
|
||||
module Chain
|
||||
module Config
|
||||
class Process < Chain::Base
|
||||
include Chain::Helpers
|
||||
|
||||
def perform!
|
||||
raise ArgumentError, 'missing config content' unless @command.config_content
|
||||
|
||||
@command.config_processor = ::Gitlab::Ci::YamlProcessor.new(
|
||||
@command.config_content, {
|
||||
project: project,
|
||||
sha: @pipeline.sha,
|
||||
user: current_user
|
||||
}
|
||||
)
|
||||
rescue Gitlab::Ci::YamlProcessor::ValidationError => ex
|
||||
error(ex.message, config_error: true)
|
||||
rescue => ex
|
||||
Gitlab::Sentry.track_acceptable_exception(ex, extra: {
|
||||
project_id: project.id,
|
||||
sha: @pipeline.sha
|
||||
})
|
||||
|
||||
error("Undefined error (#{Labkit::Correlation::CorrelationId.current_id})",
|
||||
config_error: true)
|
||||
end
|
||||
|
||||
def break?
|
||||
@pipeline.errors.any? || @pipeline.persisted?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -41,7 +41,7 @@ module Gitlab
|
|||
end
|
||||
|
||||
def workflow_config
|
||||
@pipeline.config_processor.workflow_attributes || {}
|
||||
@command.config_processor.workflow_attributes || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,29 +10,12 @@ module Gitlab
|
|||
PopulateError = Class.new(StandardError)
|
||||
|
||||
def perform!
|
||||
# Allocate next IID. This operation must be outside of transactions of pipeline creations.
|
||||
pipeline.ensure_project_iid!
|
||||
|
||||
# Protect the pipeline. This is assigned in Populate instead of
|
||||
# Build to prevent erroring out on ambiguous refs.
|
||||
pipeline.protected = @command.protected_ref?
|
||||
|
||||
##
|
||||
# Populate pipeline with block argument of CreatePipelineService#execute.
|
||||
#
|
||||
@command.seeds_block&.call(pipeline)
|
||||
|
||||
##
|
||||
# Gather all runtime build/stage errors
|
||||
#
|
||||
if seeds_errors = pipeline.stage_seeds.flat_map(&:errors).compact.presence
|
||||
return error(seeds_errors.join("\n"), config_error: true)
|
||||
end
|
||||
raise ArgumentError, 'missing stage seeds' unless @command.stage_seeds
|
||||
|
||||
##
|
||||
# Populate pipeline with all stages, and stages with builds.
|
||||
#
|
||||
pipeline.stages = pipeline.stage_seeds.map(&:to_resource)
|
||||
pipeline.stages = @command.stage_seeds.map(&:to_resource)
|
||||
|
||||
if pipeline.stages.none?
|
||||
return error('No stages / jobs for this pipeline.')
|
||||
|
|
|
@ -6,11 +6,13 @@ module Gitlab
|
|||
module Chain
|
||||
class RemoveUnwantedChatJobs < Chain::Base
|
||||
def perform!
|
||||
return unless pipeline.config_processor && pipeline.chat?
|
||||
raise ArgumentError, 'missing config processor' unless @command.config_processor
|
||||
|
||||
return unless pipeline.chat?
|
||||
|
||||
# When scheduling a chat pipeline we only want to run the build
|
||||
# that matches the chat command.
|
||||
pipeline.config_processor.jobs.select! do |name, _|
|
||||
@command.config_processor.jobs.select! do |name, _|
|
||||
name.to_s == command.chat_data[:command].to_s
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
module Pipeline
|
||||
module Chain
|
||||
class Seed < Chain::Base
|
||||
include Chain::Helpers
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
|
||||
def perform!
|
||||
raise ArgumentError, 'missing config processor' unless @command.config_processor
|
||||
|
||||
# Allocate next IID. This operation must be outside of transactions of pipeline creations.
|
||||
pipeline.ensure_project_iid!
|
||||
|
||||
# Protect the pipeline. This is assigned in Populate instead of
|
||||
# Build to prevent erroring out on ambiguous refs.
|
||||
pipeline.protected = @command.protected_ref?
|
||||
|
||||
##
|
||||
# Populate pipeline with block argument of CreatePipelineService#execute.
|
||||
#
|
||||
@command.seeds_block&.call(pipeline)
|
||||
|
||||
##
|
||||
# Gather all runtime build/stage errors
|
||||
#
|
||||
if stage_seeds_errors
|
||||
return error(stage_seeds_errors.join("\n"), config_error: true)
|
||||
end
|
||||
|
||||
@command.stage_seeds = stage_seeds
|
||||
end
|
||||
|
||||
def break?
|
||||
pipeline.errors.any?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def stage_seeds_errors
|
||||
stage_seeds.flat_map(&:errors).compact.presence
|
||||
end
|
||||
|
||||
def stage_seeds
|
||||
strong_memoize(:stage_seeds) do
|
||||
seeds = stages_attributes.inject([]) do |previous_stages, attributes|
|
||||
seed = Gitlab::Ci::Pipeline::Seed::Stage.new(pipeline, attributes, previous_stages)
|
||||
previous_stages + [seed]
|
||||
end
|
||||
|
||||
seeds.select(&:included?)
|
||||
end
|
||||
end
|
||||
|
||||
def stages_attributes
|
||||
@command.config_processor.stages_attributes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,33 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Ci
|
||||
module Pipeline
|
||||
module Chain
|
||||
module Validate
|
||||
class Config < Chain::Base
|
||||
include Chain::Helpers
|
||||
|
||||
def perform!
|
||||
unless @pipeline.config_processor
|
||||
unless @pipeline.ci_yaml_file
|
||||
return error("Missing #{@pipeline.ci_yaml_file_path} file")
|
||||
end
|
||||
|
||||
if @command.save_incompleted && @pipeline.has_yaml_errors?
|
||||
@pipeline.drop!(:config_error)
|
||||
end
|
||||
|
||||
error(@pipeline.yaml_errors)
|
||||
end
|
||||
end
|
||||
|
||||
def break?
|
||||
@pipeline.errors.any? || @pipeline.persisted?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -66,11 +66,13 @@ module Gitlab
|
|||
def move_repositories(namespace, old_full_path, new_full_path)
|
||||
repo_shards_for_namespace(namespace).each do |repository_storage|
|
||||
# Ensure old directory exists before moving it
|
||||
gitlab_shell.add_namespace(repository_storage, old_full_path)
|
||||
Gitlab::GitalyClient::NamespaceService.allow do
|
||||
gitlab_shell.add_namespace(repository_storage, old_full_path)
|
||||
|
||||
unless gitlab_shell.mv_namespace(repository_storage, old_full_path, new_full_path)
|
||||
message = "Exception moving on shard #{repository_storage} from #{old_full_path} to #{new_full_path}"
|
||||
Rails.logger.error message # rubocop:disable Gitlab/RailsLogger
|
||||
unless gitlab_shell.mv_namespace(repository_storage, old_full_path, new_full_path)
|
||||
message = "Exception moving on shard #{repository_storage} from #{old_full_path} to #{new_full_path}"
|
||||
Rails.logger.error message # rubocop:disable Gitlab/RailsLogger
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,22 @@
|
|||
module Gitlab
|
||||
module GitalyClient
|
||||
class NamespaceService
|
||||
extend Gitlab::TemporarilyAllow
|
||||
|
||||
NamespaceServiceAccessError = Class.new(StandardError)
|
||||
ALLOW_KEY = :allow_namespace
|
||||
|
||||
def self.allow
|
||||
temporarily_allow(ALLOW_KEY) { yield }
|
||||
end
|
||||
|
||||
def self.denied?
|
||||
!temporarily_allowed?(ALLOW_KEY)
|
||||
end
|
||||
|
||||
def initialize(storage)
|
||||
raise NamespaceServiceAccessError if self.class.denied?
|
||||
|
||||
@storage = storage
|
||||
end
|
||||
|
||||
|
|
|
@ -261,9 +261,6 @@ msgstr ""
|
|||
msgid "%{from} to %{to}"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{gitlab_ci_yml} not found in this commit"
|
||||
msgstr ""
|
||||
|
||||
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
|
||||
msgstr ""
|
||||
|
||||
|
@ -908,6 +905,9 @@ msgstr ""
|
|||
msgid "Add Kubernetes cluster"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add LICENSE"
|
||||
msgstr ""
|
||||
|
||||
msgid "Add README"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11947,6 +11947,9 @@ msgstr ""
|
|||
msgid "Owner"
|
||||
msgstr ""
|
||||
|
||||
msgid "Package deleted successfully"
|
||||
msgstr ""
|
||||
|
||||
msgid "Package information"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11965,10 +11968,10 @@ msgstr ""
|
|||
msgid "PackageRegistry|Copy yarn setup command"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Delete Package"
|
||||
msgid "PackageRegistry|Delete Package Version"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Delete Package Version"
|
||||
msgid "PackageRegistry|Delete package"
|
||||
msgstr ""
|
||||
|
||||
msgid "PackageRegistry|Installation"
|
||||
|
|
|
@ -12,7 +12,7 @@ module QA
|
|||
fill_in 'password', with: QA::Runtime::Env.github_password
|
||||
click_on 'Sign in'
|
||||
|
||||
Support::Retrier.retry_until(exit_on_failure: true) do
|
||||
Support::Retrier.retry_until(exit_on_failure: true, sleep_interval: 35) do
|
||||
otp = OnePassword::CLI.new.otp
|
||||
|
||||
fill_in 'otp', with: otp
|
||||
|
|
|
@ -157,39 +157,6 @@ describe 'Commits' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '.gitlab-ci.yml not found warning' do
|
||||
before do
|
||||
project.add_reporter(user)
|
||||
end
|
||||
|
||||
context 'ci builds enabled' do
|
||||
it 'does not show warning' do
|
||||
visit pipeline_path(pipeline)
|
||||
|
||||
expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
|
||||
end
|
||||
|
||||
it 'shows warning' do
|
||||
stub_ci_pipeline_yaml_file(nil)
|
||||
|
||||
visit pipeline_path(pipeline)
|
||||
|
||||
expect(page).to have_content '.gitlab-ci.yml not found in this commit'
|
||||
end
|
||||
end
|
||||
|
||||
context 'ci builds disabled' do
|
||||
it 'does not show warning' do
|
||||
stub_ci_builds_disabled
|
||||
stub_ci_pipeline_yaml_file(nil)
|
||||
|
||||
visit pipeline_path(pipeline)
|
||||
|
||||
expect(page).not_to have_content '.gitlab-ci.yml not found in this commit'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'viewing commits for a branch' do
|
||||
|
|
|
@ -57,7 +57,7 @@ describe 'Sort Issuable List' do
|
|||
it 'is "last updated"' do
|
||||
visit_merge_requests_with_state(project, 'merged')
|
||||
|
||||
expect(find('.issues-other-filters')).to have_content('Last updated')
|
||||
expect(find('.filter-dropdown-container')).to have_content('Last updated')
|
||||
expect(first_merge_request).to include(last_updated_issuable.title)
|
||||
expect(last_merge_request).to include(first_updated_issuable.title)
|
||||
end
|
||||
|
@ -69,7 +69,7 @@ describe 'Sort Issuable List' do
|
|||
it 'is "last updated"' do
|
||||
visit_merge_requests_with_state(project, 'closed')
|
||||
|
||||
expect(find('.issues-other-filters')).to have_content('Last updated')
|
||||
expect(find('.filter-dropdown-container')).to have_content('Last updated')
|
||||
expect(first_merge_request).to include(last_updated_issuable.title)
|
||||
expect(last_merge_request).to include(first_updated_issuable.title)
|
||||
end
|
||||
|
@ -81,7 +81,7 @@ describe 'Sort Issuable List' do
|
|||
it 'is "created date"' do
|
||||
visit_merge_requests_with_state(project, 'all')
|
||||
|
||||
expect(find('.issues-other-filters')).to have_content('Created date')
|
||||
expect(find('.filter-dropdown-container')).to have_content('Created date')
|
||||
expect(first_merge_request).to include(last_created_issuable.title)
|
||||
expect(last_merge_request).to include(first_created_issuable.title)
|
||||
end
|
||||
|
@ -94,7 +94,7 @@ describe 'Sort Issuable List' do
|
|||
it 'supports sorting in asc and desc order' do
|
||||
visit_merge_requests_with_state(project, 'open')
|
||||
|
||||
page.within('.issues-other-filters') do
|
||||
page.within('.filter-dropdown-container') do
|
||||
click_button('Created date')
|
||||
click_link('Last updated')
|
||||
end
|
||||
|
@ -102,7 +102,7 @@ describe 'Sort Issuable List' do
|
|||
expect(first_merge_request).to include(last_updated_issuable.title)
|
||||
expect(last_merge_request).to include(first_updated_issuable.title)
|
||||
|
||||
find('.issues-other-filters .filter-dropdown-container .rspec-reverse-sort').click
|
||||
find('.filter-dropdown-container .rspec-reverse-sort').click
|
||||
|
||||
expect(first_merge_request).to include(first_updated_issuable.title)
|
||||
expect(last_merge_request).to include(last_updated_issuable.title)
|
||||
|
@ -133,7 +133,7 @@ describe 'Sort Issuable List' do
|
|||
it 'is "created date"' do
|
||||
visit_issues project
|
||||
|
||||
expect(find('.issues-other-filters')).to have_content('Created date')
|
||||
expect(find('.filter-dropdown-container')).to have_content('Created date')
|
||||
expect(first_issue).to include(last_created_issuable.title)
|
||||
expect(last_issue).to include(first_created_issuable.title)
|
||||
end
|
||||
|
@ -145,7 +145,7 @@ describe 'Sort Issuable List' do
|
|||
it 'is "created date"' do
|
||||
visit_issues_with_state(project, 'open')
|
||||
|
||||
expect(find('.issues-other-filters')).to have_content('Created date')
|
||||
expect(find('.filter-dropdown-container')).to have_content('Created date')
|
||||
expect(first_issue).to include(last_created_issuable.title)
|
||||
expect(last_issue).to include(first_created_issuable.title)
|
||||
end
|
||||
|
@ -157,7 +157,7 @@ describe 'Sort Issuable List' do
|
|||
it 'is "last updated"' do
|
||||
visit_issues_with_state(project, 'closed')
|
||||
|
||||
expect(find('.issues-other-filters')).to have_content('Last updated')
|
||||
expect(find('.filter-dropdown-container')).to have_content('Last updated')
|
||||
expect(first_issue).to include(last_updated_issuable.title)
|
||||
expect(last_issue).to include(first_updated_issuable.title)
|
||||
end
|
||||
|
@ -169,7 +169,7 @@ describe 'Sort Issuable List' do
|
|||
it 'is "created date"' do
|
||||
visit_issues_with_state(project, 'all')
|
||||
|
||||
expect(find('.issues-other-filters')).to have_content('Created date')
|
||||
expect(find('.filter-dropdown-container')).to have_content('Created date')
|
||||
expect(first_issue).to include(last_created_issuable.title)
|
||||
expect(last_issue).to include(first_created_issuable.title)
|
||||
end
|
||||
|
@ -183,7 +183,7 @@ describe 'Sort Issuable List' do
|
|||
end
|
||||
|
||||
it 'shows the sort order as created date' do
|
||||
expect(find('.issues-other-filters')).to have_content('Created date')
|
||||
expect(find('.filter-dropdown-container')).to have_content('Created date')
|
||||
expect(first_issue).to include(last_created_issuable.title)
|
||||
expect(last_issue).to include(first_created_issuable.title)
|
||||
end
|
||||
|
@ -196,7 +196,7 @@ describe 'Sort Issuable List' do
|
|||
it 'supports sorting in asc and desc order' do
|
||||
visit_issues_with_state(project, 'open')
|
||||
|
||||
page.within('.issues-other-filters') do
|
||||
page.within('.filter-dropdown-container') do
|
||||
click_button('Created date')
|
||||
click_link('Last updated')
|
||||
end
|
||||
|
@ -204,7 +204,7 @@ describe 'Sort Issuable List' do
|
|||
expect(first_issue).to include(last_updated_issuable.title)
|
||||
expect(last_issue).to include(first_updated_issuable.title)
|
||||
|
||||
find('.issues-other-filters .filter-dropdown-container .rspec-reverse-sort').click
|
||||
find('.filter-dropdown-container .rspec-reverse-sort').click
|
||||
|
||||
expect(first_issue).to include(first_updated_issuable.title)
|
||||
expect(last_issue).to include(last_updated_issuable.title)
|
||||
|
|
|
@ -56,10 +56,6 @@ describe 'User browses commits' do
|
|||
project.enable_ci
|
||||
|
||||
create(:ci_build, pipeline: pipeline)
|
||||
|
||||
allow_next_instance_of(Ci::Pipeline) do |instance|
|
||||
allow(instance).to receive(:ci_yaml_file).and_return('')
|
||||
end
|
||||
end
|
||||
|
||||
it 'renders commit ci info' do
|
||||
|
|
|
@ -7,13 +7,11 @@ describe 'Projects > Files > User views files page' do
|
|||
let(:user) { project.owner }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
|
||||
sign_in user
|
||||
visit project_tree_path(project, project.repository.root_ref)
|
||||
end
|
||||
|
||||
it 'user sees folders and submodules sorted together, followed by files' do
|
||||
it 'user sees folders and submodules sorted together, followed by files', :js do
|
||||
rows = all('td.tree-item-file-name').map(&:text)
|
||||
tree = project.repository.tree
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@ describe 'Projects > Files > Project owner creates a license file', :js do
|
|||
let(:project_maintainer) { project.owner }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
project.repository.delete_file(project_maintainer, 'LICENSE',
|
||||
message: 'Remove LICENSE', branch_name: 'master')
|
||||
sign_in(project_maintainer)
|
||||
|
@ -39,7 +38,7 @@ describe 'Projects > Files > Project owner creates a license file', :js do
|
|||
end
|
||||
|
||||
it 'project maintainer creates a license file from the "Add license" link' do
|
||||
click_link 'Add license'
|
||||
click_link 'Add LICENSE'
|
||||
|
||||
expect(page).to have_content('New file')
|
||||
expect(current_path).to eq(
|
||||
|
|
|
@ -12,7 +12,7 @@ describe 'Projects > Files > Project owner sees a link to create a license file
|
|||
|
||||
it 'project maintainer creates a license file from a template' do
|
||||
visit project_path(project)
|
||||
click_on 'Add license'
|
||||
click_on 'Add LICENSE'
|
||||
expect(page).to have_content('New file')
|
||||
|
||||
expect(current_path).to eq(
|
||||
|
|
|
@ -13,23 +13,22 @@ describe "User browses files" do
|
|||
let(:user) { project.owner }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
it "shows last commit for current directory" do
|
||||
it "shows last commit for current directory", :js do
|
||||
visit(tree_path_root_ref)
|
||||
|
||||
click_link("files")
|
||||
|
||||
last_commit = project.repository.last_commit_for_path(project.default_branch, "files")
|
||||
|
||||
page.within(".blob-commit-info") do
|
||||
page.within(".commit-detail") do
|
||||
expect(page).to have_content(last_commit.short_id).and have_content(last_commit.author_name)
|
||||
end
|
||||
end
|
||||
|
||||
context "when browsing the master branch" do
|
||||
context "when browsing the master branch", :js do
|
||||
before do
|
||||
visit(tree_path_root_ref)
|
||||
end
|
||||
|
@ -124,8 +123,7 @@ describe "User browses files" do
|
|||
expect(current_path).to eq(project_tree_path(project, "markdown/doc/raketasks"))
|
||||
expect(page).to have_content("backup_restore.md").and have_content("maintenance.md")
|
||||
|
||||
click_link("shop")
|
||||
click_link("Maintenance")
|
||||
click_link("maintenance.md")
|
||||
|
||||
expect(current_path).to eq(project_blob_path(project, "markdown/doc/raketasks/maintenance.md"))
|
||||
expect(page).to have_content("bundle exec rake gitlab:env:info RAILS_ENV=production")
|
||||
|
@ -144,7 +142,7 @@ describe "User browses files" do
|
|||
|
||||
# rubocop:disable Lint/Void
|
||||
# Test the full URLs of links instead of relative paths by `have_link(text: "...", href: "...")`.
|
||||
find("a", text: /^empty$/)["href"] == project_tree_url(project, "markdown/d")
|
||||
find("a", text: "..")["href"] == project_tree_url(project, "markdown/d")
|
||||
# rubocop:enable Lint/Void
|
||||
|
||||
page.within(".tree-table") do
|
||||
|
@ -168,7 +166,7 @@ describe "User browses files" do
|
|||
end
|
||||
end
|
||||
|
||||
context "when browsing a specific ref" do
|
||||
context "when browsing a specific ref", :js do
|
||||
let(:ref) { project_tree_path(project, "6d39438") }
|
||||
|
||||
before do
|
||||
|
@ -180,7 +178,7 @@ describe "User browses files" do
|
|||
expect(page).to have_content(".gitignore").and have_content("LICENSE")
|
||||
end
|
||||
|
||||
it "shows files from a repository with apostroph in its name", :js do
|
||||
it "shows files from a repository with apostroph in its name" do
|
||||
first(".js-project-refs-dropdown").click
|
||||
|
||||
page.within(".project-refs-form") do
|
||||
|
@ -191,10 +189,10 @@ describe "User browses files" do
|
|||
|
||||
visit(project_tree_path(project, "'test'"))
|
||||
|
||||
expect(page).to have_css(".tree-commit-link").and have_no_content("Loading commit data...")
|
||||
expect(page).not_to have_selector(".tree-commit .animation-container")
|
||||
end
|
||||
|
||||
it "shows the code with a leading dot in the directory", :js do
|
||||
it "shows the code with a leading dot in the directory" do
|
||||
first(".js-project-refs-dropdown").click
|
||||
|
||||
page.within(".project-refs-form") do
|
||||
|
@ -203,7 +201,7 @@ describe "User browses files" do
|
|||
|
||||
visit(project_tree_path(project, "fix/.testdir"))
|
||||
|
||||
expect(page).to have_css(".tree-commit-link").and have_no_content("Loading commit data...")
|
||||
expect(page).not_to have_selector(".tree-commit .animation-container")
|
||||
end
|
||||
|
||||
it "does not show the permalink link" do
|
||||
|
@ -221,7 +219,7 @@ describe "User browses files" do
|
|||
click_link(".gitignore")
|
||||
end
|
||||
|
||||
it "shows a file content", :js do
|
||||
it "shows a file content" do
|
||||
expect(page).to have_content("*.rbc")
|
||||
end
|
||||
|
||||
|
|
|
@ -7,8 +7,6 @@ describe 'Projects > Files > User browses LFS files' do
|
|||
let(:user) { project.owner }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
|
|
|
@ -13,8 +13,6 @@ describe 'Projects > Files > User creates a directory', :js do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
|
||||
project.add_developer(user)
|
||||
sign_in(user)
|
||||
visit project_tree_path(project, 'master')
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'Projects > Files > User creates files' do
|
||||
describe 'Projects > Files > User creates files', :js do
|
||||
let(:fork_message) do
|
||||
"You're not allowed to make changes to this project directly. "\
|
||||
"A fork of this project has been created that you can make changes in, so you can submit a merge request."
|
||||
|
@ -14,7 +14,6 @@ describe 'Projects > Files > User creates files' do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
stub_feature_flags(web_ide_default: false)
|
||||
|
||||
project.add_maintainer(user)
|
||||
|
@ -68,8 +67,7 @@ describe 'Projects > Files > User creates files' do
|
|||
file_name = find('#file_name')
|
||||
file_name.set options[:file_name] || 'README.md'
|
||||
|
||||
file_content = find('#file-content', visible: false)
|
||||
file_content.set options[:file_content] || 'Some content'
|
||||
find('.ace_text-input', visible: false).send_keys.native.send_keys options[:file_content] || 'Some content'
|
||||
|
||||
click_button 'Commit changes'
|
||||
end
|
||||
|
@ -89,7 +87,7 @@ describe 'Projects > Files > User creates files' do
|
|||
expect(page).to have_content 'Path cannot include directory traversal'
|
||||
end
|
||||
|
||||
it 'creates and commit a new file', :js do
|
||||
it 'creates and commit a new file' do
|
||||
find('#editor')
|
||||
execute_script("ace.edit('editor').setValue('*.rbca')")
|
||||
fill_in(:file_name, with: 'not_a_file.md')
|
||||
|
@ -105,7 +103,7 @@ describe 'Projects > Files > User creates files' do
|
|||
expect(page).to have_content('*.rbca')
|
||||
end
|
||||
|
||||
it 'creates and commit a new file with new lines at the end of file', :js do
|
||||
it 'creates and commit a new file with new lines at the end of file' do
|
||||
find('#editor')
|
||||
execute_script('ace.edit("editor").setValue("Sample\n\n\n")')
|
||||
fill_in(:file_name, with: 'not_a_file.md')
|
||||
|
@ -122,7 +120,7 @@ describe 'Projects > Files > User creates files' do
|
|||
expect(evaluate_script('ace.edit("editor").getValue()')).to eq("Sample\n\n\n")
|
||||
end
|
||||
|
||||
it 'creates and commit a new file with a directory name', :js do
|
||||
it 'creates and commit a new file with a directory name' do
|
||||
fill_in(:file_name, with: 'foo/bar/baz.txt')
|
||||
|
||||
expect(page).to have_selector('.file-editor')
|
||||
|
@ -139,7 +137,7 @@ describe 'Projects > Files > User creates files' do
|
|||
expect(page).to have_content('*.rbca')
|
||||
end
|
||||
|
||||
it 'creates and commit a new file specifying a new branch', :js do
|
||||
it 'creates and commit a new file specifying a new branch' do
|
||||
expect(page).to have_selector('.file-editor')
|
||||
|
||||
find('#editor')
|
||||
|
@ -174,7 +172,7 @@ describe 'Projects > Files > User creates files' do
|
|||
expect(page).to have_content(message)
|
||||
end
|
||||
|
||||
it 'creates and commit new file in forked project', :js do
|
||||
it 'creates and commit new file in forked project' do
|
||||
expect(page).to have_selector('.file-editor')
|
||||
|
||||
find('#editor')
|
||||
|
|
|
@ -14,8 +14,6 @@ describe 'Projects > Files > User deletes files', :js do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ describe 'Projects > Files > User edits files', :js do
|
|||
|
||||
before do
|
||||
stub_feature_flags(web_ide_default: false)
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
|
|
@ -16,8 +16,6 @@ describe 'Projects > Files > User replaces files', :js do
|
|||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
|
||||
sign_in(user)
|
||||
end
|
||||
|
||||
|
|
|
@ -16,8 +16,6 @@ describe 'Projects > Files > User uploads files' do
|
|||
let(:project2_tree_path_root_ref) { project_tree_path(project2, project2.repository.root_ref) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe 'Projects > Show > Collaboration links' do
|
||||
describe 'Projects > Show > Collaboration links', :js do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
project.add_developer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
@ -17,15 +16,21 @@ describe 'Projects > Show > Collaboration links' do
|
|||
|
||||
# The navigation bar
|
||||
page.within('.header-new') do
|
||||
find('.qa-new-menu-toggle').click
|
||||
|
||||
aggregate_failures 'dropdown links in the navigation bar' do
|
||||
expect(page).to have_link('New issue')
|
||||
expect(page).to have_link('New merge request')
|
||||
expect(page).to have_link('New snippet', href: new_project_snippet_path(project))
|
||||
end
|
||||
|
||||
find('.qa-new-menu-toggle').click
|
||||
end
|
||||
|
||||
# The dropdown above the tree
|
||||
page.within('.repo-breadcrumb') do
|
||||
find('.qa-add-to-tree').click
|
||||
|
||||
aggregate_failures 'dropdown links above the repo tree' do
|
||||
expect(page).to have_link('New file')
|
||||
expect(page).to have_link('Upload file')
|
||||
|
@ -45,22 +50,18 @@ describe 'Projects > Show > Collaboration links' do
|
|||
visit project_path(project)
|
||||
|
||||
page.within('.header-new') do
|
||||
find('.qa-new-menu-toggle').click
|
||||
|
||||
aggregate_failures 'dropdown links' do
|
||||
expect(page).not_to have_link('New issue')
|
||||
expect(page).not_to have_link('New merge request')
|
||||
expect(page).not_to have_link('New snippet', href: new_project_snippet_path(project))
|
||||
end
|
||||
|
||||
find('.qa-new-menu-toggle').click
|
||||
end
|
||||
|
||||
page.within('.repo-breadcrumb') do
|
||||
aggregate_failures 'dropdown links' do
|
||||
expect(page).not_to have_link('New file')
|
||||
expect(page).not_to have_link('Upload file')
|
||||
expect(page).not_to have_link('New directory')
|
||||
expect(page).not_to have_link('New branch')
|
||||
expect(page).not_to have_link('New tag')
|
||||
end
|
||||
end
|
||||
expect(page).not_to have_selector('.qa-add-to-tree')
|
||||
|
||||
expect(page).not_to have_link('Web IDE')
|
||||
end
|
||||
|
|
|
@ -5,10 +5,6 @@ require 'spec_helper'
|
|||
describe 'Projects > Show > User sees last commit CI status' do
|
||||
set(:project) { create(:project, :repository, :public) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
end
|
||||
|
||||
it 'shows the project README', :js do
|
||||
project.enable_ci
|
||||
pipeline = create(:ci_pipeline, project: project, sha: project.commit.sha, ref: 'master')
|
||||
|
@ -16,9 +12,9 @@ describe 'Projects > Show > User sees last commit CI status' do
|
|||
|
||||
visit project_path(project)
|
||||
|
||||
page.within '.blob-commit-info' do
|
||||
page.within '.commit-detail' do
|
||||
expect(page).to have_content(project.commit.sha[0..6])
|
||||
expect(page).to have_link('Pipeline: skipped')
|
||||
expect(page).to have_selector('[aria-label="Commit: skipped"]')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -59,8 +59,8 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
|
|||
end
|
||||
|
||||
it '"Add license" button linked to new file populated for a license' do
|
||||
page.within('.project-stats') do
|
||||
expect(page).to have_link('Add license', href: presenter.add_license_path)
|
||||
page.within('.project-buttons') do
|
||||
expect(page).to have_link('Add LICENSE', href: presenter.add_license_path)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -175,7 +175,7 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
|
|||
expect(project.repository.license_blob).not_to be_nil
|
||||
|
||||
page.within('.project-buttons') do
|
||||
expect(page).not_to have_link('Add license')
|
||||
expect(page).not_to have_link('Add LICENSE')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ describe 'Projects tree', :js do
|
|||
let(:test_sha) { '7975be0116940bf2ad4321f79d02a55c5f7779aa' }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
project.add_maintainer(user)
|
||||
sign_in(user)
|
||||
end
|
||||
|
|
|
@ -6,10 +6,6 @@ describe 'Project' do
|
|||
include ProjectForksHelper
|
||||
include MobileHelpers
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
end
|
||||
|
||||
describe 'creating from template' do
|
||||
let(:user) { create(:user) }
|
||||
let(:template) { Gitlab::ProjectTemplate.find(:rails) }
|
||||
|
@ -272,7 +268,7 @@ describe 'Project' do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'tree view (default view is set to Files)' do
|
||||
describe 'tree view (default view is set to Files)', :js do
|
||||
let(:user) { create(:user, project_view: 'files') }
|
||||
let(:project) { create(:forked_project_with_submodules) }
|
||||
|
||||
|
@ -285,19 +281,19 @@ describe 'Project' do
|
|||
it 'has working links to files' do
|
||||
click_link('PROCESS.md')
|
||||
|
||||
expect(page.status_code).to eq(200)
|
||||
expect(page).to have_selector('.file-holder')
|
||||
end
|
||||
|
||||
it 'has working links to directories' do
|
||||
click_link('encoding')
|
||||
|
||||
expect(page.status_code).to eq(200)
|
||||
expect(page).to have_selector('.breadcrumb-item', text: 'encoding')
|
||||
end
|
||||
|
||||
it 'has working links to submodules' do
|
||||
click_link('645f6c4c')
|
||||
|
||||
expect(page.status_code).to eq(200)
|
||||
expect(page).to have_selector('.qa-branches-select', text: '645f6c4c82fd3f5e06f67134450a570b795e55a6')
|
||||
end
|
||||
|
||||
context 'for signed commit on default branch', :js do
|
||||
|
|
|
@ -173,13 +173,5 @@ describe 'GPG signed commits' do
|
|||
context 'with vue tree view enabled' do
|
||||
it_behaves_like 'a commit with a signature'
|
||||
end
|
||||
|
||||
context 'with vue tree view disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a commit with a signature'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@ let $apollo;
|
|||
const MOCK_BLOBS = [
|
||||
{
|
||||
id: '123abc',
|
||||
sha: '123abc',
|
||||
flatPath: 'blob',
|
||||
name: 'blob.md',
|
||||
type: 'blob',
|
||||
|
@ -16,6 +17,7 @@ const MOCK_BLOBS = [
|
|||
},
|
||||
{
|
||||
id: '124abc',
|
||||
sha: '124abc',
|
||||
flatPath: 'blob2',
|
||||
name: 'blob2.md',
|
||||
type: 'blob',
|
||||
|
|
|
@ -41,6 +41,7 @@ describe('Repository table row component', () => {
|
|||
it('renders table row', () => {
|
||||
factory({
|
||||
id: '1',
|
||||
sha: '123',
|
||||
path: 'test',
|
||||
type: 'file',
|
||||
currentPath: '/',
|
||||
|
@ -57,6 +58,7 @@ describe('Repository table row component', () => {
|
|||
`('renders a $componentName for type $type', ({ type, component }) => {
|
||||
factory({
|
||||
id: '1',
|
||||
sha: '123',
|
||||
path: 'test',
|
||||
type,
|
||||
currentPath: '/',
|
||||
|
@ -73,6 +75,7 @@ describe('Repository table row component', () => {
|
|||
`('pushes new router if type $type is tree', ({ type, pushes }) => {
|
||||
factory({
|
||||
id: '1',
|
||||
sha: '123',
|
||||
path: 'test',
|
||||
type,
|
||||
currentPath: '/',
|
||||
|
@ -95,6 +98,7 @@ describe('Repository table row component', () => {
|
|||
`('calls visitUrl if $type is not tree', ({ type, pushes }) => {
|
||||
factory({
|
||||
id: '1',
|
||||
sha: '123',
|
||||
path: 'test',
|
||||
type,
|
||||
currentPath: '/',
|
||||
|
@ -112,6 +116,7 @@ describe('Repository table row component', () => {
|
|||
it('renders commit ID for submodule', () => {
|
||||
factory({
|
||||
id: '1',
|
||||
sha: '123',
|
||||
path: 'test',
|
||||
type: 'commit',
|
||||
currentPath: '/',
|
||||
|
@ -123,6 +128,7 @@ describe('Repository table row component', () => {
|
|||
it('renders link with href', () => {
|
||||
factory({
|
||||
id: '1',
|
||||
sha: '123',
|
||||
path: 'test',
|
||||
type: 'blob',
|
||||
url: 'https://test.com',
|
||||
|
@ -135,6 +141,7 @@ describe('Repository table row component', () => {
|
|||
it('renders LFS badge', () => {
|
||||
factory({
|
||||
id: '1',
|
||||
sha: '123',
|
||||
path: 'test',
|
||||
type: 'commit',
|
||||
currentPath: '/',
|
||||
|
@ -147,6 +154,7 @@ describe('Repository table row component', () => {
|
|||
it('renders commit and web links with href for submodule', () => {
|
||||
factory({
|
||||
id: '1',
|
||||
sha: '123',
|
||||
path: 'test',
|
||||
type: 'commit',
|
||||
url: 'https://test.com',
|
||||
|
@ -161,6 +169,7 @@ describe('Repository table row component', () => {
|
|||
it('renders lock icon', () => {
|
||||
factory({
|
||||
id: '1',
|
||||
sha: '123',
|
||||
path: 'test',
|
||||
type: 'tree',
|
||||
currentPath: '/',
|
||||
|
|
|
@ -41,7 +41,7 @@ describe('fetchLogsTree', () => {
|
|||
|
||||
jest.spyOn(axios, 'get');
|
||||
|
||||
global.gon = { gitlab_url: 'https://test.com' };
|
||||
global.gon = { relative_url_root: '' };
|
||||
|
||||
client = {
|
||||
readQuery: () => ({
|
||||
|
@ -64,10 +64,9 @@ describe('fetchLogsTree', () => {
|
|||
|
||||
it('calls axios get', () =>
|
||||
fetchLogsTree(client, '', '0', resolver).then(() => {
|
||||
expect(axios.get).toHaveBeenCalledWith(
|
||||
'https://test.com/gitlab-org/gitlab-foss/refs/master/logs_tree/',
|
||||
{ params: { format: 'json', offset: '0' } },
|
||||
);
|
||||
expect(axios.get).toHaveBeenCalledWith('/gitlab-org/gitlab-foss/refs/master/logs_tree/', {
|
||||
params: { format: 'json', offset: '0' },
|
||||
});
|
||||
}));
|
||||
|
||||
it('calls axios get once', () =>
|
||||
|
|
|
@ -99,6 +99,19 @@ describe Resolvers::IssuesResolver do
|
|||
expect(resolve_issues(sort: :due_date_desc)).to eq [due_issue1, due_issue3, due_issue4, due_issue2]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sorting by relative position' do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
let!(:relative_issue1) { create(:issue, project: project, relative_position: 2000) }
|
||||
let!(:relative_issue2) { create(:issue, project: project, relative_position: nil) }
|
||||
let!(:relative_issue3) { create(:issue, project: project, relative_position: 1000) }
|
||||
let!(:relative_issue4) { create(:issue, project: project, relative_position: nil) }
|
||||
|
||||
it 'sorts issues ascending' do
|
||||
expect(resolve_issues(sort: :relative_position_asc)).to eq [relative_issue3, relative_issue1, relative_issue4, relative_issue2]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns issues user can see' do
|
||||
|
|
|
@ -10,7 +10,7 @@ describe GitlabSchema.types['Commit'] do
|
|||
it 'contains attributes related to commit' do
|
||||
expect(described_class).to have_graphql_fields(
|
||||
:id, :sha, :title, :description, :message, :authored_date,
|
||||
:author, :web_url, :latest_pipeline, :pipelines, :signature_html
|
||||
:author_name, :author, :web_url, :latest_pipeline, :pipelines, :signature_html
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,6 @@ describe GitlabSchema.types['IssueSort'] do
|
|||
it_behaves_like 'common sort values'
|
||||
|
||||
it 'exposes all the existing issue sort values' do
|
||||
expect(described_class.values.keys).to include(*%w[DUE_DATE_ASC DUE_DATE_DESC])
|
||||
expect(described_class.values.keys).to include(*%w[DUE_DATE_ASC DUE_DATE_DESC RELATIVE_POSITION_ASC])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,5 +5,5 @@ require 'spec_helper'
|
|||
describe Types::Tree::BlobType do
|
||||
it { expect(described_class.graphql_name).to eq('Blob') }
|
||||
|
||||
it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url, :lfs_oid) }
|
||||
it { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :lfs_oid) }
|
||||
end
|
||||
|
|
|
@ -5,5 +5,5 @@ require 'spec_helper'
|
|||
describe Types::Tree::SubmoduleType do
|
||||
it { expect(described_class.graphql_name).to eq('Submodule') }
|
||||
|
||||
it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url, :tree_url) }
|
||||
it { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url, :tree_url) }
|
||||
end
|
||||
|
|
|
@ -5,5 +5,5 @@ require 'spec_helper'
|
|||
describe Types::Tree::TreeEntryType do
|
||||
it { expect(described_class.graphql_name).to eq('TreeEntry') }
|
||||
|
||||
it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url) }
|
||||
it { expect(described_class).to have_graphql_fields(:id, :sha, :name, :type, :path, :flat_path, :web_url) }
|
||||
end
|
||||
|
|
|
@ -68,4 +68,52 @@ describe 'lograge', type: :request do
|
|||
subject
|
||||
end
|
||||
end
|
||||
|
||||
context 'with a log subscriber' do
|
||||
let(:subscriber) { Lograge::RequestLogSubscriber.new }
|
||||
|
||||
let(:event) do
|
||||
ActiveSupport::Notifications::Event.new(
|
||||
'process_action.action_controller',
|
||||
Time.now,
|
||||
Time.now,
|
||||
2,
|
||||
status: 200,
|
||||
controller: 'HomeController',
|
||||
action: 'index',
|
||||
format: 'application/json',
|
||||
method: 'GET',
|
||||
path: '/home?foo=bar',
|
||||
params: {},
|
||||
db_runtime: 0.02,
|
||||
view_runtime: 0.01
|
||||
)
|
||||
end
|
||||
|
||||
let(:log_output) { StringIO.new }
|
||||
let(:logger) do
|
||||
Logger.new(log_output).tap { |logger| logger.formatter = ->(_, _, _, msg) { msg } }
|
||||
end
|
||||
|
||||
describe 'with an exception' do
|
||||
let(:exception) { RuntimeError.new('bad request') }
|
||||
let(:backtrace) { caller }
|
||||
|
||||
before do
|
||||
allow(exception).to receive(:backtrace).and_return(backtrace)
|
||||
event.payload[:exception_object] = exception
|
||||
Lograge.logger = logger
|
||||
end
|
||||
|
||||
it 'adds exception data to log' do
|
||||
subscriber.process_action(event)
|
||||
|
||||
log_data = JSON.parse(log_output.string)
|
||||
|
||||
expect(log_data['exception']['class']).to eq('RuntimeError')
|
||||
expect(log_data['exception']['message']).to eq('bad request')
|
||||
expect(log_data['exception']['backtrace']).to eq(Gitlab::Profiler.clean_backtrace(backtrace))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import Vue from 'vue';
|
||||
import BoardService from '~/boards/services/board_service';
|
||||
import BoardsSelector from '~/boards/components/boards_selector.vue';
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { TEST_HOST } from 'spec/test_constants';
|
||||
|
@ -37,7 +36,6 @@ describe('BoardsSelector', () => {
|
|||
bulkUpdatePath: '',
|
||||
boardId: '',
|
||||
});
|
||||
window.gl.boardService = new BoardService();
|
||||
|
||||
allBoardsResponse = Promise.resolve({
|
||||
data: boards,
|
||||
|
@ -46,8 +44,8 @@ describe('BoardsSelector', () => {
|
|||
data: recentBoards,
|
||||
});
|
||||
|
||||
spyOn(BoardService.prototype, 'allBoards').and.returnValue(allBoardsResponse);
|
||||
spyOn(BoardService.prototype, 'recentBoards').and.returnValue(recentBoardsResponse);
|
||||
spyOn(boardsStore, 'allBoards').and.returnValue(allBoardsResponse);
|
||||
spyOn(boardsStore, 'recentBoards').and.returnValue(recentBoardsResponse);
|
||||
|
||||
const Component = Vue.extend(BoardsSelector);
|
||||
vm = mountComponent(
|
||||
|
@ -94,7 +92,6 @@ describe('BoardsSelector', () => {
|
|||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
window.gl.boardService = undefined;
|
||||
});
|
||||
|
||||
describe('filtering', () => {
|
||||
|
|
|
@ -11,6 +11,7 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
|
|||
[{ key: 'first', secret_value: 'world' },
|
||||
{ key: 'second', secret_value: 'second_world' }]
|
||||
end
|
||||
|
||||
let(:command) do
|
||||
Gitlab::Ci::Pipeline::Chain::Command.new(
|
||||
source: :push,
|
||||
|
@ -51,12 +52,6 @@ describe Gitlab::Ci::Pipeline::Chain::Build do
|
|||
.to eq variables_attributes.map(&:with_indifferent_access)
|
||||
end
|
||||
|
||||
it 'sets a valid config source' do
|
||||
step.perform!
|
||||
|
||||
expect(pipeline.repository_source?).to be true
|
||||
end
|
||||
|
||||
it 'returns a valid pipeline' do
|
||||
step.perform!
|
||||
|
||||
|
|
|
@ -18,19 +18,32 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
|
|||
seeds_block: nil)
|
||||
end
|
||||
|
||||
let(:dependencies) do
|
||||
[
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command),
|
||||
Gitlab::Ci::Pipeline::Chain::Seed.new(pipeline, command)
|
||||
]
|
||||
end
|
||||
|
||||
let(:step) { described_class.new(pipeline, command) }
|
||||
|
||||
let(:config) do
|
||||
{ rspec: { script: 'rspec' } }
|
||||
end
|
||||
|
||||
def run_chain
|
||||
dependencies.map(&:perform!)
|
||||
step.perform!
|
||||
end
|
||||
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(YAML.dump(config))
|
||||
end
|
||||
|
||||
context 'when pipeline doesn not have seeds block' do
|
||||
before do
|
||||
step.perform!
|
||||
run_chain
|
||||
end
|
||||
|
||||
it 'does not persist the pipeline' do
|
||||
|
@ -66,7 +79,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
|
|||
end
|
||||
|
||||
before do
|
||||
step.perform!
|
||||
run_chain
|
||||
end
|
||||
|
||||
it 'breaks the chain' do
|
||||
|
@ -84,16 +97,16 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
|
|||
end
|
||||
|
||||
describe 'pipeline protect' do
|
||||
subject { step.perform! }
|
||||
|
||||
context 'when ref is protected' do
|
||||
before do
|
||||
allow(project).to receive(:protected_for?).with('master').and_return(true)
|
||||
allow(project).to receive(:protected_for?).with('refs/heads/master').and_return(true)
|
||||
|
||||
dependencies.map(&:perform!)
|
||||
end
|
||||
|
||||
it 'does not protect the pipeline' do
|
||||
subject
|
||||
run_chain
|
||||
|
||||
expect(pipeline.protected).to eq(true)
|
||||
end
|
||||
|
@ -101,7 +114,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
|
|||
|
||||
context 'when ref is not protected' do
|
||||
it 'does not protect the pipeline' do
|
||||
subject
|
||||
run_chain
|
||||
|
||||
expect(pipeline.protected).to eq(false)
|
||||
end
|
||||
|
@ -114,7 +127,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
|
|||
end
|
||||
|
||||
before do
|
||||
step.perform!
|
||||
run_chain
|
||||
end
|
||||
|
||||
it 'breaks the chain' do
|
||||
|
@ -146,7 +159,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
|
|||
end
|
||||
|
||||
it 'populates pipeline with resources described in the seeds block' do
|
||||
step.perform!
|
||||
run_chain
|
||||
|
||||
expect(pipeline).not_to be_persisted
|
||||
expect(pipeline.variables).not_to be_empty
|
||||
|
@ -156,7 +169,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
|
|||
end
|
||||
|
||||
it 'has pipeline iid' do
|
||||
step.perform!
|
||||
run_chain
|
||||
|
||||
expect(pipeline.iid).to be > 0
|
||||
end
|
||||
|
@ -168,7 +181,7 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
|
|||
end
|
||||
|
||||
it 'wastes pipeline iid' do
|
||||
expect { step.perform! }.to raise_error(ActiveRecord::RecordNotSaved)
|
||||
expect { run_chain }.to raise_error(ActiveRecord::RecordNotSaved)
|
||||
|
||||
last_iid = InternalId.ci_pipelines
|
||||
.where(project_id: project.id)
|
||||
|
@ -183,14 +196,14 @@ describe Gitlab::Ci::Pipeline::Chain::Populate do
|
|||
let(:pipeline) { create(:ci_pipeline, project: project) }
|
||||
|
||||
it 'raises error' do
|
||||
expect { step.perform! }.to raise_error(described_class::PopulateError)
|
||||
expect { run_chain }.to raise_error(described_class::PopulateError)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when variables policy is specified' do
|
||||
shared_examples_for 'a correct pipeline' do
|
||||
it 'populates pipeline according to used policies' do
|
||||
step.perform!
|
||||
run_chain
|
||||
|
||||
expect(pipeline.stages.size).to eq 1
|
||||
expect(pipeline.stages.first.statuses.size).to eq 1
|
||||
|
|
|
@ -2,36 +2,38 @@
|
|||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
|
||||
let(:project) { create(:project, :repository) }
|
||||
describe ::Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs do
|
||||
let(:project) { create(:project) }
|
||||
|
||||
let(:pipeline) do
|
||||
build(:ci_pipeline, project: project)
|
||||
end
|
||||
|
||||
let(:command) do
|
||||
double(:command, project: project, chat_data: { command: 'echo' })
|
||||
end
|
||||
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(YAML.dump(rspec: { script: 'rspec' }))
|
||||
double(:command,
|
||||
config_processor: double(:processor,
|
||||
jobs: { echo: double(:job_echo), rspec: double(:job_rspec) }),
|
||||
project: project,
|
||||
chat_data: { command: 'echo' })
|
||||
end
|
||||
|
||||
describe '#perform!' do
|
||||
subject { described_class.new(pipeline, command).perform! }
|
||||
|
||||
it 'removes unwanted jobs for chat pipelines' do
|
||||
allow(pipeline).to receive(:chat?).and_return(true)
|
||||
expect(pipeline).to receive(:chat?).and_return(true)
|
||||
|
||||
pipeline.config_processor.jobs[:echo] = double(:job)
|
||||
subject
|
||||
|
||||
described_class.new(pipeline, command).perform!
|
||||
expect(command.config_processor.jobs.keys).to eq([:echo])
|
||||
end
|
||||
|
||||
expect(pipeline.config_processor.jobs.keys).to eq([:echo])
|
||||
it 'does not remove any jobs for non chat-pipelines' do
|
||||
expect(pipeline).to receive(:chat?).and_return(false)
|
||||
|
||||
subject
|
||||
|
||||
expect(command.config_processor.jobs.keys).to eq([:echo, :rspec])
|
||||
end
|
||||
end
|
||||
|
||||
it 'does not remove any jobs for non-chat pipelines' do
|
||||
described_class.new(pipeline, command).perform!
|
||||
|
||||
expect(pipeline.config_processor.jobs.keys).to eq([:rspec])
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,161 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Ci::Pipeline::Chain::Seed do
|
||||
let(:project) { create(:project, :repository) }
|
||||
let(:user) { create(:user, developer_projects: [project]) }
|
||||
|
||||
let(:command) do
|
||||
Gitlab::Ci::Pipeline::Chain::Command.new(
|
||||
project: project,
|
||||
current_user: user,
|
||||
origin_ref: 'master',
|
||||
seeds_block: nil)
|
||||
end
|
||||
|
||||
def run_chain(pipeline, command)
|
||||
[
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Content.new(pipeline, command),
|
||||
Gitlab::Ci::Pipeline::Chain::Config::Process.new(pipeline, command)
|
||||
].map(&:perform!)
|
||||
|
||||
described_class.new(pipeline, command).perform!
|
||||
end
|
||||
|
||||
let(:pipeline) { build(:ci_pipeline, project: project) }
|
||||
|
||||
describe '#perform!' do
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(YAML.dump(config))
|
||||
run_chain(pipeline, command)
|
||||
end
|
||||
|
||||
let(:config) do
|
||||
{ rspec: { script: 'rake' } }
|
||||
end
|
||||
|
||||
it 'allocates next IID' do
|
||||
expect(pipeline.iid).to be_present
|
||||
end
|
||||
|
||||
it 'sets the seeds in the command object' do
|
||||
expect(command.stage_seeds).to all(be_a Gitlab::Ci::Pipeline::Seed::Base)
|
||||
expect(command.stage_seeds.count).to eq 1
|
||||
end
|
||||
|
||||
context 'when no ref policy is specified' do
|
||||
let(:config) do
|
||||
{
|
||||
production: { stage: 'deploy', script: 'cap prod' },
|
||||
rspec: { stage: 'test', script: 'rspec' },
|
||||
spinach: { stage: 'test', script: 'spinach' }
|
||||
}
|
||||
end
|
||||
|
||||
it 'correctly fabricates a stage seeds object' do
|
||||
seeds = command.stage_seeds
|
||||
expect(seeds.size).to eq 2
|
||||
expect(seeds.first.attributes[:name]).to eq 'test'
|
||||
expect(seeds.second.attributes[:name]).to eq 'deploy'
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'rspec'
|
||||
expect(seeds.dig(0, 1, :name)).to eq 'spinach'
|
||||
expect(seeds.dig(1, 0, :name)).to eq 'production'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when refs policy is specified' do
|
||||
let(:pipeline) do
|
||||
build(:ci_pipeline, project: project, ref: 'feature', tag: true)
|
||||
end
|
||||
|
||||
let(:config) do
|
||||
{
|
||||
production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
|
||||
spinach: { stage: 'test', script: 'spinach', only: ['tags'] }
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns stage seeds only assigned to master' do
|
||||
seeds = command.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.first.attributes[:name]).to eq 'test'
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when source policy is specified' do
|
||||
let(:pipeline) { create(:ci_pipeline, source: :schedule) }
|
||||
|
||||
let(:config) do
|
||||
{
|
||||
production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] },
|
||||
spinach: { stage: 'test', script: 'spinach', only: ['schedules'] }
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns stage seeds only assigned to schedules' do
|
||||
seeds = command.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.first.attributes[:name]).to eq 'test'
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when kubernetes policy is specified' do
|
||||
let(:config) do
|
||||
{
|
||||
spinach: { stage: 'test', script: 'spinach' },
|
||||
production: {
|
||||
stage: 'deploy',
|
||||
script: 'cap',
|
||||
only: { kubernetes: 'active' }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'when kubernetes is active' do
|
||||
context 'when user configured kubernetes from CI/CD > Clusters' do
|
||||
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
let(:pipeline) { build(:ci_pipeline, project: project) }
|
||||
|
||||
it 'returns seeds for kubernetes dependent job' do
|
||||
seeds = command.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 2
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
expect(seeds.dig(1, 0, :name)).to eq 'production'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when kubernetes is not active' do
|
||||
it 'does not return seeds for kubernetes dependent job' do
|
||||
seeds = command.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when variables policy is specified' do
|
||||
let(:config) do
|
||||
{
|
||||
unit: { script: 'minitest', only: { variables: ['$CI_PIPELINE_SOURCE'] } },
|
||||
feature: { script: 'spinach', only: { variables: ['$UNDEFINED'] } }
|
||||
}
|
||||
end
|
||||
|
||||
it 'returns stage seeds only when variables expression is truthy' do
|
||||
seeds = command.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'unit'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,165 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
describe Gitlab::Ci::Pipeline::Chain::Validate::Config do
|
||||
set(:project) { create(:project, :repository) }
|
||||
set(:user) { create(:user) }
|
||||
|
||||
let(:command) do
|
||||
Gitlab::Ci::Pipeline::Chain::Command.new(
|
||||
project: project,
|
||||
current_user: user,
|
||||
save_incompleted: true)
|
||||
end
|
||||
|
||||
let(:pipeline) do
|
||||
build(:ci_pipeline, project: project)
|
||||
end
|
||||
|
||||
let!(:step) { described_class.new(pipeline, command) }
|
||||
|
||||
subject { step.perform! }
|
||||
|
||||
context 'when pipeline has no YAML configuration' do
|
||||
let(:pipeline) do
|
||||
build_stubbed(:ci_pipeline, project: project)
|
||||
end
|
||||
|
||||
it 'appends errors about missing configuration' do
|
||||
subject
|
||||
|
||||
expect(pipeline.errors.to_a)
|
||||
.to include 'Missing .gitlab-ci.yml file'
|
||||
end
|
||||
|
||||
it 'breaks the chain' do
|
||||
subject
|
||||
|
||||
expect(step.break?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when YAML configuration contains errors' do
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file('invalid YAML')
|
||||
subject
|
||||
end
|
||||
|
||||
it 'appends errors about YAML errors' do
|
||||
expect(pipeline.errors.to_a)
|
||||
.to include 'Invalid configuration format'
|
||||
end
|
||||
|
||||
it 'breaks the chain' do
|
||||
expect(step.break?).to be true
|
||||
end
|
||||
|
||||
context 'when saving incomplete pipeline is allowed' do
|
||||
let(:command) do
|
||||
double('command', project: project,
|
||||
current_user: user,
|
||||
save_incompleted: true)
|
||||
end
|
||||
|
||||
it 'fails the pipeline' do
|
||||
subject
|
||||
|
||||
expect(pipeline.reload).to be_failed
|
||||
end
|
||||
|
||||
it 'sets a config error failure reason' do
|
||||
subject
|
||||
|
||||
expect(pipeline.reload.config_error?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when saving incomplete pipeline is not allowed' do
|
||||
let(:command) do
|
||||
double('command', project: project,
|
||||
current_user: user,
|
||||
save_incompleted: false)
|
||||
end
|
||||
|
||||
it 'does not drop pipeline' do
|
||||
subject
|
||||
|
||||
expect(pipeline).not_to be_failed
|
||||
expect(pipeline).not_to be_persisted
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline contains configuration validation errors' do
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(YAML.dump({
|
||||
rspec: {
|
||||
before_script: 10,
|
||||
script: 'ls -al'
|
||||
}
|
||||
}))
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it 'appends configuration validation errors to pipeline errors' do
|
||||
expect(pipeline.errors.to_a)
|
||||
.to include "jobs:rspec:before_script config should be an array containing strings and arrays of strings"
|
||||
end
|
||||
|
||||
it 'breaks the chain' do
|
||||
expect(step.break?).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline is correct and complete' do
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(YAML.dump({
|
||||
rspec: {
|
||||
script: 'rspec'
|
||||
}
|
||||
}))
|
||||
subject
|
||||
end
|
||||
|
||||
it 'does not invalidate the pipeline' do
|
||||
expect(pipeline).to be_valid
|
||||
end
|
||||
|
||||
it 'does not break the chain' do
|
||||
expect(step.break?).to be false
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline source is merge request' do
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(YAML.dump(config))
|
||||
subject
|
||||
end
|
||||
|
||||
let(:pipeline) { build_stubbed(:ci_pipeline, project: project) }
|
||||
|
||||
let(:merge_request_pipeline) do
|
||||
build(:ci_pipeline, source: :merge_request_event, project: project)
|
||||
end
|
||||
|
||||
let(:chain) { described_class.new(merge_request_pipeline, command).tap(&:perform!) }
|
||||
|
||||
context "when config contains 'merge_requests' keyword" do
|
||||
let(:config) { { rspec: { script: 'echo', only: ['merge_requests'] } } }
|
||||
|
||||
it 'does not break the chain' do
|
||||
expect(chain).not_to be_break
|
||||
end
|
||||
end
|
||||
|
||||
context "when config contains 'merge_request' keyword" do
|
||||
let(:config) { { rspec: { script: 'echo', only: ['merge_request'] } } }
|
||||
|
||||
it 'does not break the chain' do
|
||||
expect(chain).not_to be_break
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -401,7 +401,7 @@ describe Gitlab::Shell do
|
|||
|
||||
describe '#add_namespace' do
|
||||
it 'creates a namespace' do
|
||||
subject.add_namespace(storage, "mepmep")
|
||||
Gitlab::GitalyClient::NamespaceService.allow { subject.add_namespace(storage, "mepmep") }
|
||||
|
||||
expect(TestEnv.storage_dir_exists?(storage, "mepmep")).to be(true)
|
||||
end
|
||||
|
@ -425,8 +425,10 @@ describe Gitlab::Shell do
|
|||
|
||||
describe '#remove' do
|
||||
it 'removes the namespace' do
|
||||
subject.add_namespace(storage, "mepmep")
|
||||
subject.rm_namespace(storage, "mepmep")
|
||||
Gitlab::GitalyClient::NamespaceService.allow do
|
||||
subject.add_namespace(storage, "mepmep")
|
||||
subject.rm_namespace(storage, "mepmep")
|
||||
end
|
||||
|
||||
expect(TestEnv.storage_dir_exists?(storage, "mepmep")).to be(false)
|
||||
end
|
||||
|
@ -434,8 +436,10 @@ describe Gitlab::Shell do
|
|||
|
||||
describe '#mv_namespace' do
|
||||
it 'renames the namespace' do
|
||||
subject.add_namespace(storage, "mepmep")
|
||||
subject.mv_namespace(storage, "mepmep", "2mep")
|
||||
Gitlab::GitalyClient::NamespaceService.allow do
|
||||
subject.add_namespace(storage, "mepmep")
|
||||
subject.mv_namespace(storage, "mepmep", "2mep")
|
||||
end
|
||||
|
||||
expect(TestEnv.storage_dir_exists?(storage, "mepmep")).to be(false)
|
||||
expect(TestEnv.storage_dir_exists?(storage, "2mep")).to be(true)
|
||||
|
|
|
@ -2221,7 +2221,7 @@ describe Ci::Build do
|
|||
{ key: 'CI_PAGES_URL', value: project.pages_url, public: true, masked: false },
|
||||
{ key: 'CI_API_V4_URL', value: 'http://localhost/api/v4', public: true, masked: false },
|
||||
{ key: 'CI_PIPELINE_IID', value: pipeline.iid.to_s, public: true, masked: false },
|
||||
{ key: 'CI_CONFIG_PATH', value: pipeline.ci_yaml_file_path, public: true, masked: false },
|
||||
{ key: 'CI_CONFIG_PATH', value: pipeline.config_path, public: true, masked: false },
|
||||
{ key: 'CI_PIPELINE_SOURCE', value: pipeline.source, public: true, masked: false },
|
||||
{ key: 'CI_COMMIT_MESSAGE', value: pipeline.git_commit_message, public: true, masked: false },
|
||||
{ key: 'CI_COMMIT_TITLE', value: pipeline.git_commit_title, public: true, masked: false },
|
||||
|
@ -2667,11 +2667,17 @@ describe Ci::Build do
|
|||
it { is_expected.to include(deployment_variable) }
|
||||
end
|
||||
|
||||
context 'when project has default CI config path' do
|
||||
let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: '.gitlab-ci.yml', public: true, masked: false } }
|
||||
|
||||
it { is_expected.to include(ci_config_path) }
|
||||
end
|
||||
|
||||
context 'when project has custom CI config path' do
|
||||
let(:ci_config_path) { { key: 'CI_CONFIG_PATH', value: 'custom', public: true, masked: false } }
|
||||
|
||||
before do
|
||||
project.update(ci_config_path: 'custom')
|
||||
expect_any_instance_of(Project).to receive(:ci_config_path) { 'custom' }
|
||||
end
|
||||
|
||||
it { is_expected.to include(ci_config_path) }
|
||||
|
|
|
@ -979,149 +979,6 @@ describe Ci::Pipeline, :mailer do
|
|||
end
|
||||
|
||||
describe 'pipeline stages' do
|
||||
describe '#stage_seeds' do
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(YAML.dump(config))
|
||||
end
|
||||
|
||||
let(:pipeline) { build(:ci_pipeline) }
|
||||
let(:config) { { rspec: { script: 'rake' } } }
|
||||
|
||||
it 'returns preseeded stage seeds object' do
|
||||
expect(pipeline.stage_seeds)
|
||||
.to all(be_a Gitlab::Ci::Pipeline::Seed::Base)
|
||||
expect(pipeline.stage_seeds.count).to eq 1
|
||||
end
|
||||
|
||||
context 'when no refs policy is specified' do
|
||||
let(:config) do
|
||||
{ production: { stage: 'deploy', script: 'cap prod' },
|
||||
rspec: { stage: 'test', script: 'rspec' },
|
||||
spinach: { stage: 'test', script: 'spinach' } }
|
||||
end
|
||||
|
||||
it 'correctly fabricates a stage seeds object' do
|
||||
seeds = pipeline.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 2
|
||||
expect(seeds.first.attributes[:name]).to eq 'test'
|
||||
expect(seeds.second.attributes[:name]).to eq 'deploy'
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'rspec'
|
||||
expect(seeds.dig(0, 1, :name)).to eq 'spinach'
|
||||
expect(seeds.dig(1, 0, :name)).to eq 'production'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when refs policy is specified' do
|
||||
let(:pipeline) do
|
||||
build(:ci_pipeline, ref: 'feature', tag: true)
|
||||
end
|
||||
|
||||
let(:config) do
|
||||
{ production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
|
||||
spinach: { stage: 'test', script: 'spinach', only: ['tags'] } }
|
||||
end
|
||||
|
||||
it 'returns stage seeds only assigned to master to master' do
|
||||
seeds = pipeline.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.first.attributes[:name]).to eq 'test'
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when source policy is specified' do
|
||||
let(:pipeline) { build(:ci_pipeline, source: :schedule) }
|
||||
|
||||
let(:config) do
|
||||
{ production: { stage: 'deploy', script: 'cap prod', only: ['triggers'] },
|
||||
spinach: { stage: 'test', script: 'spinach', only: ['schedules'] } }
|
||||
end
|
||||
|
||||
it 'returns stage seeds only assigned to schedules' do
|
||||
seeds = pipeline.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.first.attributes[:name]).to eq 'test'
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
end
|
||||
end
|
||||
|
||||
context 'when kubernetes policy is specified' do
|
||||
let(:config) do
|
||||
{
|
||||
spinach: { stage: 'test', script: 'spinach' },
|
||||
production: {
|
||||
stage: 'deploy',
|
||||
script: 'cap',
|
||||
only: { kubernetes: 'active' }
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'when kubernetes is active' do
|
||||
context 'when user configured kubernetes from CI/CD > Clusters' do
|
||||
let!(:cluster) { create(:cluster, :project, :provided_by_gcp) }
|
||||
let(:project) { cluster.project }
|
||||
let(:pipeline) { build(:ci_pipeline, project: project) }
|
||||
|
||||
it 'returns seeds for kubernetes dependent job' do
|
||||
seeds = pipeline.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 2
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
expect(seeds.dig(1, 0, :name)).to eq 'production'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when kubernetes is not active' do
|
||||
it 'does not return seeds for kubernetes dependent job' do
|
||||
seeds = pipeline.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'spinach'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when variables policy is specified' do
|
||||
let(:config) do
|
||||
{ unit: { script: 'minitest', only: { variables: ['$CI_PIPELINE_SOURCE'] } },
|
||||
feature: { script: 'spinach', only: { variables: ['$UNDEFINED'] } } }
|
||||
end
|
||||
|
||||
it 'returns stage seeds only when variables expression is truthy' do
|
||||
seeds = pipeline.stage_seeds
|
||||
|
||||
expect(seeds.size).to eq 1
|
||||
expect(seeds.dig(0, 0, :name)).to eq 'unit'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#seeds_size' do
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(YAML.dump(config))
|
||||
end
|
||||
|
||||
context 'when refs policy is specified' do
|
||||
let(:config) do
|
||||
{ production: { stage: 'deploy', script: 'cap prod', only: ['master'] },
|
||||
spinach: { stage: 'test', script: 'spinach', only: ['tags'] } }
|
||||
end
|
||||
|
||||
let(:pipeline) do
|
||||
build(:ci_pipeline, ref: 'feature', tag: true)
|
||||
end
|
||||
|
||||
it 'returns real seeds size' do
|
||||
expect(pipeline.seeds_size).to eq 1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'legacy stages' do
|
||||
before do
|
||||
create(:commit_status, pipeline: pipeline,
|
||||
|
@ -2194,161 +2051,6 @@ describe Ci::Pipeline, :mailer do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#ci_yaml_file_path' do
|
||||
subject { pipeline.ci_yaml_file_path }
|
||||
|
||||
%i[unknown_source repository_source].each do |source|
|
||||
context source.to_s do
|
||||
before do
|
||||
pipeline.config_source = described_class.config_sources.fetch(source)
|
||||
end
|
||||
|
||||
it 'returns the path from project' do
|
||||
allow(pipeline.project).to receive(:ci_config_path) { 'custom/path' }
|
||||
|
||||
is_expected.to eq('custom/path')
|
||||
end
|
||||
|
||||
it 'returns default when custom path is nil' do
|
||||
allow(pipeline.project).to receive(:ci_config_path) { nil }
|
||||
|
||||
is_expected.to eq('.gitlab-ci.yml')
|
||||
end
|
||||
|
||||
it 'returns default when custom path is empty' do
|
||||
allow(pipeline.project).to receive(:ci_config_path) { '' }
|
||||
|
||||
is_expected.to eq('.gitlab-ci.yml')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline is for auto-devops' do
|
||||
before do
|
||||
pipeline.config_source = 'auto_devops_source'
|
||||
end
|
||||
|
||||
it 'does not return config file' do
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#set_config_source' do
|
||||
context 'when pipelines does not contain needed data and auto devops is disabled' do
|
||||
before do
|
||||
stub_application_setting(auto_devops_enabled: false)
|
||||
end
|
||||
|
||||
it 'defines source to be unknown' do
|
||||
pipeline.set_config_source
|
||||
|
||||
expect(pipeline).to be_unknown_source
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline contains all needed data' do
|
||||
let(:pipeline) do
|
||||
create(:ci_pipeline, project: project,
|
||||
sha: '1234',
|
||||
ref: 'master',
|
||||
source: :push)
|
||||
end
|
||||
|
||||
context 'when the repository has a config file' do
|
||||
before do
|
||||
allow(project.repository).to receive(:gitlab_ci_yml_for)
|
||||
.and_return('config')
|
||||
end
|
||||
|
||||
it 'defines source to be from repository' do
|
||||
pipeline.set_config_source
|
||||
|
||||
expect(pipeline).to be_repository_source
|
||||
end
|
||||
|
||||
context 'when loading an object' do
|
||||
let(:new_pipeline) { Ci::Pipeline.find(pipeline.id) }
|
||||
|
||||
it 'does not redefine the source' do
|
||||
# force to overwrite the source
|
||||
pipeline.unknown_source!
|
||||
|
||||
expect(new_pipeline).to be_unknown_source
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the repository does not have a config file' do
|
||||
let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
|
||||
|
||||
context 'auto devops enabled' do
|
||||
before do
|
||||
allow(project).to receive(:ci_config_path) { 'custom' }
|
||||
end
|
||||
|
||||
it 'defines source to be auto devops' do
|
||||
pipeline.set_config_source
|
||||
|
||||
expect(pipeline).to be_auto_devops_source
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#ci_yaml_file' do
|
||||
let(:implied_yml) { Gitlab::Template::GitlabCiYmlTemplate.find('Auto-DevOps').content }
|
||||
|
||||
context 'the source is unknown' do
|
||||
before do
|
||||
pipeline.unknown_source!
|
||||
end
|
||||
|
||||
it 'returns the configuration if found' do
|
||||
allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
|
||||
.and_return('config')
|
||||
|
||||
expect(pipeline.ci_yaml_file).to be_a(String)
|
||||
expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
|
||||
expect(pipeline.yaml_errors).to be_nil
|
||||
end
|
||||
|
||||
it 'sets yaml errors if not found' do
|
||||
expect(pipeline.ci_yaml_file).to be_nil
|
||||
expect(pipeline.yaml_errors)
|
||||
.to start_with('Failed to load CI/CD config file')
|
||||
end
|
||||
end
|
||||
|
||||
context 'the source is the repository' do
|
||||
before do
|
||||
pipeline.repository_source!
|
||||
end
|
||||
|
||||
it 'returns the configuration if found' do
|
||||
allow(pipeline.project.repository).to receive(:gitlab_ci_yml_for)
|
||||
.and_return('config')
|
||||
|
||||
expect(pipeline.ci_yaml_file).to be_a(String)
|
||||
expect(pipeline.ci_yaml_file).not_to eq(implied_yml)
|
||||
expect(pipeline.yaml_errors).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the source is auto_devops_source' do
|
||||
before do
|
||||
stub_application_setting(auto_devops_enabled: true)
|
||||
pipeline.auto_devops_source!
|
||||
end
|
||||
|
||||
it 'finds the implied config' do
|
||||
expect(pipeline.ci_yaml_file).to eq(implied_yml)
|
||||
expect(pipeline.yaml_errors).to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#update_status' do
|
||||
context 'when pipeline is empty' do
|
||||
it 'updates does not change pipeline status' do
|
||||
|
@ -2894,49 +2596,19 @@ describe Ci::Pipeline, :mailer do
|
|||
end
|
||||
|
||||
describe '#has_yaml_errors?' do
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(YAML.dump(config))
|
||||
end
|
||||
|
||||
let(:pipeline) { create(:ci_pipeline) }
|
||||
|
||||
context 'when pipeline has errors' do
|
||||
let(:config) { { rspec: nil } }
|
||||
|
||||
it 'contains yaml errors' do
|
||||
pipeline.config_processor
|
||||
context 'when yaml_errors is set' do
|
||||
before do
|
||||
pipeline.yaml_errors = 'File not found'
|
||||
end
|
||||
|
||||
it 'returns true if yaml_errors is set' do
|
||||
expect(pipeline).to have_yaml_errors
|
||||
expect(pipeline.yaml_errors).to include('contains unknown keys')
|
||||
expect(pipeline.yaml_errors).to include('File not foun')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline has undefined error' do
|
||||
let(:config) { double(:config) }
|
||||
|
||||
it 'contains yaml errors' do
|
||||
expect(::Gitlab::Ci::YamlProcessor).to receive(:new)
|
||||
.and_raise(RuntimeError, 'undefined failure')
|
||||
|
||||
expect(Gitlab::Sentry).to receive(:track_acceptable_exception)
|
||||
.with(be_a(RuntimeError), anything)
|
||||
.and_call_original
|
||||
|
||||
pipeline.config_processor
|
||||
|
||||
expect(pipeline).to have_yaml_errors
|
||||
expect(pipeline.yaml_errors).to include('Undefined error')
|
||||
end
|
||||
end
|
||||
|
||||
context 'when pipeline does not have errors' do
|
||||
let(:config) do
|
||||
{ rspec: { script: 'rake test' } }
|
||||
end
|
||||
|
||||
it 'does not contain yaml errors' do
|
||||
expect(pipeline).not_to have_yaml_errors
|
||||
end
|
||||
it 'returns false if yaml_errors is not set' do
|
||||
expect(pipeline).not_to have_yaml_errors
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -312,8 +312,8 @@ describe ProjectPresenter do
|
|||
project.add_developer(user)
|
||||
allow(project.repository).to receive(:license_blob).and_return(nil)
|
||||
|
||||
expect(presenter.license_anchor_data).to have_attributes(is_link: true,
|
||||
label: a_string_including('Add license'),
|
||||
expect(presenter.license_anchor_data).to have_attributes(is_link: false,
|
||||
label: a_string_including('Add LICENSE'),
|
||||
link: presenter.add_license_path)
|
||||
end
|
||||
end
|
||||
|
@ -322,7 +322,7 @@ describe ProjectPresenter do
|
|||
it 'returns anchor data' do
|
||||
allow(project.repository).to receive(:license_blob).and_return(double(name: 'foo'))
|
||||
|
||||
expect(presenter.license_anchor_data).to have_attributes(is_link: true,
|
||||
expect(presenter.license_anchor_data).to have_attributes(is_link: false,
|
||||
label: a_string_including(presenter.license_short_name),
|
||||
link: presenter.license_path)
|
||||
end
|
||||
|
@ -420,6 +420,7 @@ describe ProjectPresenter do
|
|||
|
||||
it 'orders the items correctly' do
|
||||
allow(project.repository).to receive(:readme).and_return(double(name: 'readme'))
|
||||
allow(project.repository).to receive(:license_blob).and_return(nil)
|
||||
allow(project.repository).to receive(:changelog).and_return(nil)
|
||||
allow(project.repository).to receive(:contribution_guide).and_return(double(name: 'foo'))
|
||||
allow(presenter).to receive(:filename_path).and_return('fake/path')
|
||||
|
@ -433,25 +434,54 @@ describe ProjectPresenter do
|
|||
end
|
||||
end
|
||||
|
||||
describe '#empty_repo_statistics_buttons' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
describe '#repo_statistics_buttons' do
|
||||
let(:presenter) { described_class.new(project, current_user: user) }
|
||||
|
||||
subject(:empty_repo_statistics_buttons) { presenter.empty_repo_statistics_buttons }
|
||||
|
||||
before do
|
||||
project.add_developer(user)
|
||||
allow(project).to receive(:auto_devops_enabled?).and_return(false)
|
||||
end
|
||||
|
||||
it 'orders the items correctly in an empty project' do
|
||||
expect(empty_repo_statistics_buttons.map(&:label)).to start_with(
|
||||
a_string_including('New'),
|
||||
a_string_including('README'),
|
||||
a_string_including('CHANGELOG'),
|
||||
a_string_including('CONTRIBUTING'),
|
||||
a_string_including('CI/CD')
|
||||
)
|
||||
context 'empty repo' do
|
||||
let(:project) { create(:project, :stubbed_repository)}
|
||||
|
||||
context 'for a guest user' do
|
||||
it 'orders the items correctly' do
|
||||
expect(empty_repo_statistics_buttons.map(&:label)).to start_with(
|
||||
a_string_including('No license')
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'for a developer' do
|
||||
before do
|
||||
project.add_developer(user)
|
||||
end
|
||||
|
||||
it 'orders the items correctly' do
|
||||
expect(empty_repo_statistics_buttons.map(&:label)).to start_with(
|
||||
a_string_including('New'),
|
||||
a_string_including('README'),
|
||||
a_string_including('LICENSE'),
|
||||
a_string_including('CHANGELOG'),
|
||||
a_string_including('CONTRIBUTING'),
|
||||
a_string_including('CI/CD')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'initialized repo' do
|
||||
let(:project) { create(:project, :repository) }
|
||||
|
||||
it 'orders the items correctly' do
|
||||
expect(empty_repo_statistics_buttons.map(&:label)).to start_with(
|
||||
a_string_including('README'),
|
||||
a_string_including('License'),
|
||||
a_string_including('CHANGELOG'),
|
||||
a_string_including('CONTRIBUTING')
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -200,6 +200,52 @@ describe 'getting an issue list for a project' do
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when sorting by relative position' do
|
||||
let(:sort_project) { create(:project, :public) }
|
||||
|
||||
let!(:relative_issue1) { create(:issue, project: sort_project, relative_position: 2000) }
|
||||
let!(:relative_issue2) { create(:issue, project: sort_project, relative_position: nil) }
|
||||
let!(:relative_issue3) { create(:issue, project: sort_project, relative_position: 1000) }
|
||||
let!(:relative_issue4) { create(:issue, project: sort_project, relative_position: nil) }
|
||||
let!(:relative_issue5) { create(:issue, project: sort_project, relative_position: 500) }
|
||||
|
||||
let(:params) { 'sort: RELATIVE_POSITION_ASC' }
|
||||
|
||||
def query(issue_params = params)
|
||||
graphql_query_for(
|
||||
'project',
|
||||
{ 'fullPath' => sort_project.full_path },
|
||||
"issues(#{issue_params}) { pageInfo { endCursor} edges { node { iid dueDate } } }"
|
||||
)
|
||||
end
|
||||
|
||||
before do
|
||||
post_graphql(query, current_user: current_user)
|
||||
end
|
||||
|
||||
it_behaves_like 'a working graphql query'
|
||||
|
||||
context 'when ascending' do
|
||||
it 'sorts issues' do
|
||||
expect(grab_iids).to eq [relative_issue5.iid, relative_issue3.iid, relative_issue1.iid, relative_issue4.iid, relative_issue2.iid]
|
||||
end
|
||||
|
||||
context 'when paginating' do
|
||||
let(:params) { 'sort: RELATIVE_POSITION_ASC, first: 2' }
|
||||
|
||||
it 'sorts issues' do
|
||||
expect(grab_iids).to eq [relative_issue5.iid, relative_issue3.iid]
|
||||
|
||||
cursored_query = query("sort: RELATIVE_POSITION_ASC, after: \"#{end_cursor}\"")
|
||||
post_graphql(cursored_query, current_user: current_user)
|
||||
response_data = JSON.parse(response.body)['data']['project']['issues']['edges']
|
||||
|
||||
expect(grab_iids(response_data)).to eq [relative_issue1.iid, relative_issue4.iid, relative_issue2.iid]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def grab_iids(data = issues_data)
|
||||
|
|
|
@ -1043,14 +1043,12 @@ describe API::MergeRequests do
|
|||
|
||||
describe 'POST /projects/:id/merge_requests/:merge_request_iid/pipelines' do
|
||||
before do
|
||||
allow_any_instance_of(Ci::Pipeline)
|
||||
.to receive(:ci_yaml_file)
|
||||
.and_return(YAML.dump({
|
||||
rspec: {
|
||||
script: 'ls',
|
||||
only: ['merge_requests']
|
||||
}
|
||||
}))
|
||||
stub_ci_pipeline_yaml_file(YAML.dump({
|
||||
rspec: {
|
||||
script: 'ls',
|
||||
only: ['merge_requests']
|
||||
}
|
||||
}))
|
||||
end
|
||||
|
||||
let(:project) do
|
||||
|
|
|
@ -65,6 +65,7 @@ describe Ci::CreatePipelineService do
|
|||
expect(pipeline.iid).not_to be_nil
|
||||
expect(pipeline.repository_source?).to be true
|
||||
expect(pipeline.builds.first).to be_kind_of(Ci::Build)
|
||||
expect(pipeline.yaml_errors).not_to be_present
|
||||
end
|
||||
|
||||
it 'increments the prometheus counter' do
|
||||
|
@ -474,6 +475,66 @@ describe Ci::CreatePipelineService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'config evaluation' do
|
||||
context 'when config is in a file in repository' do
|
||||
before do
|
||||
content = YAML.dump(rspec: { script: 'echo' })
|
||||
stub_ci_pipeline_yaml_file(content)
|
||||
end
|
||||
|
||||
it 'pull it from the repository' do
|
||||
pipeline = execute_service
|
||||
expect(pipeline).to be_repository_source
|
||||
expect(pipeline.builds.map(&:name)).to eq ['rspec']
|
||||
end
|
||||
end
|
||||
|
||||
context 'when config is from Auto-DevOps' do
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(nil)
|
||||
allow_any_instance_of(Project).to receive(:auto_devops_enabled?).and_return(true)
|
||||
end
|
||||
|
||||
it 'pull it from Auto-DevOps' do
|
||||
pipeline = execute_service
|
||||
expect(pipeline).to be_auto_devops_source
|
||||
expect(pipeline.builds.map(&:name)).to eq %w[test code_quality build]
|
||||
end
|
||||
end
|
||||
|
||||
context 'when config is not found' do
|
||||
before do
|
||||
stub_ci_pipeline_yaml_file(nil)
|
||||
end
|
||||
|
||||
it 'attaches errors to the pipeline' do
|
||||
pipeline = execute_service
|
||||
|
||||
expect(pipeline.errors.full_messages).to eq ['Missing .gitlab-ci.yml file']
|
||||
expect(pipeline).not_to be_persisted
|
||||
end
|
||||
end
|
||||
|
||||
context 'when an unexpected error is raised' do
|
||||
before do
|
||||
expect(Gitlab::Ci::YamlProcessor).to receive(:new)
|
||||
.and_raise(RuntimeError, 'undefined failure')
|
||||
end
|
||||
|
||||
it 'saves error in pipeline' do
|
||||
pipeline = execute_service
|
||||
|
||||
expect(pipeline.yaml_errors).to include('Undefined error')
|
||||
end
|
||||
|
||||
it 'logs error' do
|
||||
expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
|
||||
|
||||
execute_service
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when yaml is invalid' do
|
||||
let(:ci_yaml) { 'invalid: file: fiile' }
|
||||
let(:message) { 'Message' }
|
||||
|
@ -539,6 +600,25 @@ describe Ci::CreatePipelineService do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when an unexpected error is raised' do
|
||||
before do
|
||||
expect(Gitlab::Ci::YamlProcessor).to receive(:new)
|
||||
.and_raise(RuntimeError, 'undefined failure')
|
||||
end
|
||||
|
||||
it 'saves error in pipeline' do
|
||||
pipeline = execute_service
|
||||
|
||||
expect(pipeline.yaml_errors).to include('Undefined error')
|
||||
end
|
||||
|
||||
it 'logs error' do
|
||||
expect(Gitlab::Sentry).to receive(:track_acceptable_exception).and_call_original
|
||||
|
||||
execute_service
|
||||
end
|
||||
end
|
||||
|
||||
context 'when commit contains a [ci skip] directive' do
|
||||
let(:message) { "some message[ci skip]" }
|
||||
|
||||
|
|
|
@ -18,8 +18,13 @@ module StubGitlabCalls
|
|||
stub_ci_pipeline_yaml_file(gitlab_ci_yaml)
|
||||
end
|
||||
|
||||
def stub_ci_pipeline_yaml_file(ci_yaml)
|
||||
allow_any_instance_of(Ci::Pipeline).to receive(:ci_yaml_file) { ci_yaml }
|
||||
def stub_ci_pipeline_yaml_file(ci_yaml_content)
|
||||
allow_any_instance_of(Repository).to receive(:gitlab_ci_yml_for).and_return(ci_yaml_content)
|
||||
|
||||
# Ensure we don't hit auto-devops when config not found in repository
|
||||
unless ci_yaml_content
|
||||
allow_any_instance_of(Project).to receive(:auto_devops_enabled?).and_return(false)
|
||||
end
|
||||
end
|
||||
|
||||
def stub_pipeline_modified_paths(pipeline, modified_paths)
|
||||
|
|
|
@ -18,6 +18,14 @@ describe 'projects/show' do
|
|||
end
|
||||
|
||||
context 'commit signatures' do
|
||||
context 'with vue tree view enabled' do
|
||||
it 'are not rendered via js-signature-container' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_css('.js-signature-container')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with vue tree view disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
|
@ -29,13 +37,5 @@ describe 'projects/show' do
|
|||
expect(rendered).to have_css('.js-signature-container')
|
||||
end
|
||||
end
|
||||
|
||||
context 'with vue tree view enabled' do
|
||||
it 'are not rendered via js-signature-container' do
|
||||
render
|
||||
|
||||
expect(rendered).not_to have_css('.js-signature-container')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,8 +13,6 @@ describe 'projects/tree/show' do
|
|||
let(:tree) { repository.tree(commit.id, path) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
|
||||
assign(:project, project)
|
||||
assign(:repository, repository)
|
||||
assign(:lfs_blob_ids, [])
|
||||
|
@ -39,12 +37,15 @@ describe 'projects/tree/show' do
|
|||
render
|
||||
|
||||
expect(rendered).to have_css('.js-project-refs-dropdown .dropdown-toggle-text', text: ref)
|
||||
expect(rendered).to have_css('.readme-holder')
|
||||
end
|
||||
end
|
||||
|
||||
context 'commit signatures' do
|
||||
context 'with vue tree view disabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: false)
|
||||
end
|
||||
|
||||
it 'rendered via js-signature-container' do
|
||||
render
|
||||
|
||||
|
@ -53,10 +54,6 @@ describe 'projects/tree/show' do
|
|||
end
|
||||
|
||||
context 'with vue tree view enabled' do
|
||||
before do
|
||||
stub_feature_flags(vue_file_list: true)
|
||||
end
|
||||
|
||||
it 'are not rendered via js-signature-container' do
|
||||
render
|
||||
|
||||
|
|
Loading…
Reference in New Issue