Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-11-14 15:06:30 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2019-11-14 15:06:30 +0300
commitd8c06be498acbfc2024c01b6b6b02d120dc499f2 (patch)
tree9e2e0852c45332d6222898676a2f6f096e600084
parent2fa7d2ddf6a7004f89616e43b8279229af831e25 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_card_list_item.vue44
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue36
-rw-r--r--app/assets/javascripts/repository/components/last_commit.vue17
-rw-r--r--app/assets/javascripts/repository/components/preview/index.vue2
-rw-r--r--app/assets/javascripts/repository/queries/pathLastCommit.query.graphql18
-rw-r--r--app/assets/javascripts/repository/utils/readme.js8
-rw-r--r--app/controllers/concerns/issuable_collections.rb2
-rw-r--r--app/controllers/groups/boards_controller.rb2
-rw-r--r--app/controllers/projects/boards_controller.rb2
-rw-r--r--app/graphql/resolvers/base_resolver.rb8
-rw-r--r--app/graphql/resolvers/commit_pipelines_resolver.rb13
-rw-r--r--app/graphql/types/commit_type.rb10
-rw-r--r--app/models/group.rb8
-rw-r--r--app/models/merge_request.rb25
-rw-r--r--app/models/merge_request_diff.rb6
-rw-r--r--app/views/layouts/header/_current_user_dropdown.html.haml2
-rw-r--r--changelogs/unreleased/36141-update-start-a-trial-option-in-top-right-drop-down-to-include-gold.yml5
-rw-r--r--changelogs/unreleased/36213-update-codequality-to-12-5.yml5
-rw-r--r--changelogs/unreleased/dz-move-project-routes.yml5
-rw-r--r--changelogs/unreleased/ff-user-ids-per-scope-fe.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-group-structure-export-api-endp.yml5
-rw-r--r--changelogs/unreleased/id-avoid-preloading-merge-request-commits.yml5
-rw-r--r--config/routes/project.rb55
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql57
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json342
-rw-r--r--doc/user/application_security/container_scanning/index.md29
-rw-r--r--doc/user/project/clusters/serverless/index.md35
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/group_export.rb34
-rw-r--r--lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml2
-rw-r--r--lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb25
-rw-r--r--locale/gitlab.pot5
-rw-r--r--spec/features/projects/files/user_reads_pipeline_status_spec.rb4
-rw-r--r--spec/frontend/cycle_analytics/stage_nav_item_spec.js44
-rw-r--r--spec/frontend/repository/components/last_commit_spec.js4
-rw-r--r--spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap2
-rw-r--r--spec/frontend/repository/components/tree_content_spec.js2
-rw-r--r--spec/frontend/repository/utils/readme_spec.js33
-rw-r--r--spec/graphql/resolvers/base_resolver_spec.rb24
-rw-r--r--spec/graphql/resolvers/commit_pipelines_resolver_spec.rb53
-rw-r--r--spec/graphql/types/commit_type_spec.rb2
-rw-r--r--spec/javascripts/ci_variable_list/ajax_variable_list_spec.js2
-rw-r--r--spec/lib/gitlab/fogbugz_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb20
-rw-r--r--spec/models/merge_request_diff_spec.rb8
-rw-r--r--spec/models/merge_request_spec.rb46
-rw-r--r--spec/requests/api/group_export_spec.rb94
-rw-r--r--spec/support/helpers/access_matchers_helpers.rb95
-rw-r--r--spec/support/matchers/access_matchers_for_request.rb53
-rw-r--r--spec/support/matchers/access_matchers_generic.rb66
50 files changed, 1009 insertions, 363 deletions
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_card_list_item.vue b/app/assets/javascripts/cycle_analytics/components/stage_card_list_item.vue
deleted file mode 100644
index fc6d83bf96c..00000000000
--- a/app/assets/javascripts/cycle_analytics/components/stage_card_list_item.vue
+++ /dev/null
@@ -1,44 +0,0 @@
-<script>
-import Icon from '~/vue_shared/components/icon.vue';
-import { GlButton } from '@gitlab/ui';
-
-export default {
- name: 'StageCardListItem',
- components: {
- Icon,
- GlButton,
- },
- props: {
- isActive: {
- type: Boolean,
- required: true,
- },
- canEdit: {
- type: Boolean,
- default: false,
- required: false,
- },
- },
-};
-</script>
-
-<template>
- <div
- :class="{ active: isActive }"
- class="stage-nav-item d-flex pl-4 pr-4 m-0 mb-1 ml-2 rounded border-color-default border-style-solid border-width-1px"
- >
- <slot></slot>
- <div v-if="canEdit" class="dropdown">
- <gl-button
- :title="__('More actions')"
- class="more-actions-toggle btn btn-transparent p-0"
- data-toggle="dropdown"
- >
- <icon class="icon" name="ellipsis_v" />
- </gl-button>
- <ul class="more-actions-dropdown dropdown-menu dropdown-open-left">
- <slot name="dropdown-options"></slot>
- </ul>
- </div>
- </div>
-</template>
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue b/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue
index 004d335f572..1b09fe1b370 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_nav_item.vue
@@ -1,11 +1,6 @@
<script>
-import StageCardListItem from './stage_card_list_item.vue';
-
export default {
name: 'StageNavItem',
- components: {
- StageCardListItem,
- },
props: {
isDefaultStage: {
type: Boolean,
@@ -40,16 +35,16 @@ export default {
hasValue() {
return this.value && this.value.length > 0;
},
- editable() {
- return this.isUserAllowed && this.canEdit;
- },
},
};
</script>
<template>
<li @click="$emit('select')">
- <stage-card-list-item :is-active="isActive" :can-edit="editable">
+ <div
+ :class="{ active: isActive }"
+ class="stage-nav-item d-flex pl-4 pr-4 m-0 mb-1 ml-2 rounded border-color-default border-style-solid border-width-1px"
+ >
<div class="stage-nav-item-cell stage-name p-0" :class="{ 'font-weight-bold': isActive }">
{{ title }}
</div>
@@ -62,27 +57,6 @@ export default {
<span class="not-available">{{ __('Not available') }}</span>
</template>
</div>
- <template v-slot:dropdown-options>
- <template v-if="isDefaultStage">
- <li>
- <button type="button" class="btn-default btn-transparent">
- {{ __('Hide stage') }}
- </button>
- </li>
- </template>
- <template v-else>
- <li>
- <button type="button" class="btn-default btn-transparent">
- {{ __('Edit stage') }}
- </button>
- </li>
- <li>
- <button type="button" class="btn-danger danger">
- {{ __('Remove stage') }}
- </button>
- </li>
- </template>
- </template>
- </stage-card-list-item>
+ </div>
</li>
</template>
diff --git a/app/assets/javascripts/repository/components/last_commit.vue b/app/assets/javascripts/repository/components/last_commit.vue
index 5cbe8d6247a..5a89efa4538 100644
--- a/app/assets/javascripts/repository/components/last_commit.vue
+++ b/app/assets/javascripts/repository/components/last_commit.vue
@@ -38,7 +38,14 @@ export default {
path: this.currentPath.replace(/^\//, ''),
};
},
- update: data => data.project.repository.tree.lastCommit,
+ update: data => {
+ const pipelines = data.project.repository.tree.lastCommit.pipelines.edges;
+
+ return {
+ ...data.project.repository.tree.lastCommit,
+ pipeline: pipelines.length && pipelines[0].node,
+ };
+ },
context: {
isSingleRequest: true,
},
@@ -61,7 +68,7 @@ export default {
computed: {
statusTitle() {
return sprintf(s__('Commits|Commit: %{commitText}'), {
- commitText: this.commit.latestPipeline.detailedStatus.text,
+ commitText: this.commit.pipeline.detailedStatus.text,
});
},
isLoading() {
@@ -127,14 +134,14 @@ export default {
<div v-if="commit.signatureHtml" v-html="commit.signatureHtml"></div>
<div class="ci-status-link">
<gl-link
- v-if="commit.latestPipeline"
+ v-if="commit.pipeline"
v-gl-tooltip.left
- :href="commit.latestPipeline.detailedStatus.detailsPath"
+ :href="commit.pipeline.detailedStatus.detailsPath"
:title="statusTitle"
class="js-commit-pipeline"
>
<ci-icon
- :status="commit.latestPipeline.detailedStatus"
+ :status="commit.pipeline.detailedStatus"
:size="24"
:aria-label="statusTitle"
/>
diff --git a/app/assets/javascripts/repository/components/preview/index.vue b/app/assets/javascripts/repository/components/preview/index.vue
index 564be211c46..7f974838359 100644
--- a/app/assets/javascripts/repository/components/preview/index.vue
+++ b/app/assets/javascripts/repository/components/preview/index.vue
@@ -34,7 +34,7 @@ export default {
</script>
<template>
- <article class="file-holder js-hide-on-navigation limited-width-container readme-holder">
+ <article class="file-holder limited-width-container readme-holder">
<div class="file-title">
<i aria-hidden="true" class="fa fa-file-text-o fa-fw"></i>
<gl-link :href="blob.webUrl">
diff --git a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
index 71c1bf12749..74ccdd79dd0 100644
--- a/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
+++ b/app/assets/javascripts/repository/queries/pathLastCommit.query.graphql
@@ -14,13 +14,17 @@ query pathLastCommit($projectPath: ID!, $path: String, $ref: String!) {
webUrl
}
signatureHtml
- latestPipeline {
- detailedStatus {
- detailsPath
- icon
- tooltip
- text
- group
+ pipelines(ref: $ref, first: 1) {
+ edges {
+ node {
+ detailedStatus {
+ detailsPath
+ icon
+ tooltip
+ text
+ group
+ }
+ }
}
}
}
diff --git a/app/assets/javascripts/repository/utils/readme.js b/app/assets/javascripts/repository/utils/readme.js
index b219b857c66..e43b2bdc33a 100644
--- a/app/assets/javascripts/repository/utils/readme.js
+++ b/app/assets/javascripts/repository/utils/readme.js
@@ -3,7 +3,11 @@ const ASCIIDOC_EXTENSIONS = ['adoc', 'ad', 'asciidoc'];
const OTHER_EXTENSIONS = ['textile', 'rdoc', 'org', 'creole', 'wiki', 'mediawiki', 'rst'];
const EXTENSIONS = [...MARKDOWN_EXTENSIONS, ...ASCIIDOC_EXTENSIONS, ...OTHER_EXTENSIONS];
const PLAIN_FILENAMES = ['readme', 'index'];
-const FILE_REGEXP = new RegExp(`^(${PLAIN_FILENAMES.join('|')})`, 'i');
+const FILE_REGEXP = new RegExp(
+ `^(${PLAIN_FILENAMES.join('|')})(.(${EXTENSIONS.join('|')}))?$`,
+ 'i',
+);
+const PLAIN_FILE_REGEXP = new RegExp(`^(${PLAIN_FILENAMES.join('|')})`, 'i');
const EXTENSIONS_REGEXP = new RegExp(`.(${EXTENSIONS.join('|')})$`, 'i');
// eslint-disable-next-line import/prefer-default-export
@@ -11,7 +15,7 @@ export const readmeFile = blobs => {
const readMeFiles = blobs.filter(f => f.name.search(FILE_REGEXP) !== -1);
const previewableReadme = readMeFiles.find(f => f.name.search(EXTENSIONS_REGEXP) !== -1);
- const plainReadme = readMeFiles.find(f => f.name.search(FILE_REGEXP) !== -1);
+ const plainReadme = readMeFiles.find(f => f.name.search(PLAIN_FILE_REGEXP) !== -1);
return previewableReadme || plainReadme;
};
diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb
index c9a8de0b290..5aa00af8910 100644
--- a/app/controllers/concerns/issuable_collections.rb
+++ b/app/controllers/concerns/issuable_collections.rb
@@ -148,7 +148,7 @@ module IssuableCollections
when 'Issue'
common_attributes + [:project, project: :namespace]
when 'MergeRequest'
- common_attributes + [:target_project, source_project: :route, head_pipeline: :project, target_project: :namespace, latest_merge_request_diff: :merge_request_diff_commits]
+ common_attributes + [:target_project, :latest_merge_request_diff, source_project: :route, head_pipeline: :project, target_project: :namespace]
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 3c86f3108ab..8c9bf17f017 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -6,7 +6,7 @@ class Groups::BoardsController < Groups::ApplicationController
before_action :assign_endpoint_vars
before_action do
- push_frontend_feature_flag(:multi_select_board)
+ push_frontend_feature_flag(:multi_select_board, default_enabled: true)
end
private
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 3b335fa4af4..db05da0bb7f 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -8,7 +8,7 @@ class Projects::BoardsController < Projects::ApplicationController
before_action :authorize_read_board!, only: [:index, :show]
before_action :assign_endpoint_vars
before_action do
- push_frontend_feature_flag(:multi_select_board)
+ push_frontend_feature_flag(:multi_select_board, default_enabled: true)
end
private
diff --git a/app/graphql/resolvers/base_resolver.rb b/app/graphql/resolvers/base_resolver.rb
index 5b7eb57841c..85d6b377934 100644
--- a/app/graphql/resolvers/base_resolver.rb
+++ b/app/graphql/resolvers/base_resolver.rb
@@ -10,6 +10,14 @@ module Resolvers
end
end
+ def self.last
+ @last ||= Class.new(self) do
+ def resolve(**args)
+ super.last
+ end
+ end
+ end
+
def self.resolver_complexity(args, child_complexity:)
complexity = 1
complexity += 1 if args[:sort]
diff --git a/app/graphql/resolvers/commit_pipelines_resolver.rb b/app/graphql/resolvers/commit_pipelines_resolver.rb
new file mode 100644
index 00000000000..92a83523593
--- /dev/null
+++ b/app/graphql/resolvers/commit_pipelines_resolver.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module Resolvers
+ class CommitPipelinesResolver < BaseResolver
+ include ::ResolvesPipelines
+
+ alias_method :commit, :object
+
+ def resolve(**args)
+ resolve_pipelines(commit.project, args.merge!({ sha: commit.sha }))
+ end
+ end
+end
diff --git a/app/graphql/types/commit_type.rb b/app/graphql/types/commit_type.rb
index d5600881728..dcf4a2802c7 100644
--- a/app/graphql/types/commit_type.rb
+++ b/app/graphql/types/commit_type.rb
@@ -29,12 +29,16 @@ module Types
field :author, type: Types::UserType, null: true,
description: 'Author of the commit'
+ field :pipelines, Types::Ci::PipelineType.connection_type,
+ null: true,
+ description: 'Pipelines of the commit ordered latest first',
+ resolver: Resolvers::CommitPipelinesResolver
+
field :latest_pipeline,
type: Types::Ci::PipelineType,
null: true,
description: "Latest pipeline of the commit",
- resolve: -> (obj, ctx, args) do
- Gitlab::Graphql::Loaders::PipelineForShaLoader.new(obj.project, obj.sha).find_last
- end
+ deprecation_reason: 'use pipelines',
+ resolver: Resolvers::CommitPipelinesResolver.last
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 7496fee0b51..8289d4f099c 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -451,6 +451,14 @@ class Group < Namespace
false
end
+ def export_file_exists?
+ export_file&.file
+ end
+
+ def export_file
+ import_export_upload&.export_file
+ end
+
private
def update_two_factor_requirement
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5cf2ded114d..df516009397 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -212,8 +212,8 @@ class MergeRequest < ApplicationRecord
scope :join_project, -> { joins(:target_project) }
scope :references_project, -> { references(:target_project) }
scope :with_api_entity_associations, -> {
- preload(:assignees, :author, :unresolved_notes, :labels, :milestone, :timelogs,
- latest_merge_request_diff: [:merge_request_diff_commits],
+ preload(:assignees, :author, :unresolved_notes, :labels, :milestone,
+ :timelogs, :latest_merge_request_diff,
metrics: [:latest_closed_by, :merged_by],
target_project: [:route, { namespace: :route }],
source_project: [:route, { namespace: :route }])
@@ -396,14 +396,17 @@ class MergeRequest < ApplicationRecord
end
end
- def commit_shas
- if persisted?
- merge_request_diff.commit_shas
- elsif compare_commits
- compare_commits.to_a.reverse.map(&:sha)
- else
- Array(diff_head_sha)
- end
+ def commit_shas(limit: nil)
+ return merge_request_diff.commit_shas(limit: limit) if persisted?
+
+ shas =
+ if compare_commits
+ compare_commits.to_a.reverse.map(&:sha)
+ else
+ Array(diff_head_sha)
+ end
+
+ limit ? shas.take(limit) : shas
end
# Returns true if there are commits that match at least one commit SHA.
@@ -913,7 +916,7 @@ class MergeRequest < ApplicationRecord
def commit_notes
# Fetch comments only from last 100 commits
- commit_ids = commit_shas.take(100)
+ commit_ids = commit_shas(limit: 100)
Note
.user
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 735ad046f22..5fe97a13a42 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -218,7 +218,7 @@ class MergeRequestDiff < ApplicationRecord
end
def last_commit_sha
- commit_shas.first
+ commit_shas(limit: 1).first
end
def first_commit
@@ -247,8 +247,8 @@ class MergeRequestDiff < ApplicationRecord
project.commit_by(oid: head_commit_sha)
end
- def commit_shas
- merge_request_diff_commits.map(&:sha)
+ def commit_shas(limit: nil)
+ merge_request_diff_commits.limit(limit).pluck(:sha)
end
def commits_by_shas(shas)
diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml
index 8fb335c3801..d15f0ae3228 100644
--- a/app/views/layouts/header/_current_user_dropdown.html.haml
+++ b/app/views/layouts/header/_current_user_dropdown.html.haml
@@ -21,7 +21,7 @@
- if current_user_menu?(:start_trial)
%li
%a.profile-link{ href: trials_link_url }
- = s_("CurrentUser|Start a trial")
+ = s_("CurrentUser|Start a Gold trial")
= emoji_icon('rocket')
- if current_user_menu?(:settings)
%li
diff --git a/changelogs/unreleased/36141-update-start-a-trial-option-in-top-right-drop-down-to-include-gold.yml b/changelogs/unreleased/36141-update-start-a-trial-option-in-top-right-drop-down-to-include-gold.yml
new file mode 100644
index 00000000000..6952e630e2d
--- /dev/null
+++ b/changelogs/unreleased/36141-update-start-a-trial-option-in-top-right-drop-down-to-include-gold.yml
@@ -0,0 +1,5 @@
+---
+title: Update start a trial option in top right drop down to include Gold
+merge_request: 19971
+author:
+type: changed
diff --git a/changelogs/unreleased/36213-update-codequality-to-12-5.yml b/changelogs/unreleased/36213-update-codequality-to-12-5.yml
new file mode 100644
index 00000000000..5b4429af81e
--- /dev/null
+++ b/changelogs/unreleased/36213-update-codequality-to-12-5.yml
@@ -0,0 +1,5 @@
+---
+title: Update registry.gitlab.com/gitlab-org/security-products/codequality to 12-5-stable
+merge_request: 20046
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/dz-move-project-routes.yml b/changelogs/unreleased/dz-move-project-routes.yml
new file mode 100644
index 00000000000..713f6d90f32
--- /dev/null
+++ b/changelogs/unreleased/dz-move-project-routes.yml
@@ -0,0 +1,5 @@
+---
+title: Move some project routes under - scope
+merge_request: 19954
+author:
+type: deprecated
diff --git a/changelogs/unreleased/ff-user-ids-per-scope-fe.yml b/changelogs/unreleased/ff-user-ids-per-scope-fe.yml
deleted file mode 100644
index 2d928ce8c8a..00000000000
--- a/changelogs/unreleased/ff-user-ids-per-scope-fe.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make User IDs work per scope in Feature Flags
-merge_request: 19399
-author:
-type: added
diff --git a/changelogs/unreleased/georgekoltsov-group-structure-export-api-endp.yml b/changelogs/unreleased/georgekoltsov-group-structure-export-api-endp.yml
new file mode 100644
index 00000000000..ed14e958b7f
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-group-structure-export-api-endp.yml
@@ -0,0 +1,5 @@
+---
+title: Add API endpoint to trigger Group Structure Export
+merge_request: 19779
+author:
+type: added
diff --git a/changelogs/unreleased/id-avoid-preloading-merge-request-commits.yml b/changelogs/unreleased/id-avoid-preloading-merge-request-commits.yml
new file mode 100644
index 00000000000..e937b8f2e6e
--- /dev/null
+++ b/changelogs/unreleased/id-avoid-preloading-merge-request-commits.yml
@@ -0,0 +1,5 @@
+---
+title: Execute limited request for diff commits instead of preloading
+merge_request: 19485
+author:
+type: performance
diff --git a/config/routes/project.rb b/config/routes/project.rb
index dacc433784d..3f913683b00 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -191,6 +191,31 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get 'proxy/:datasource_id/*proxy_path', to: 'grafana_api#proxy'
get :metrics_dashboard, to: 'grafana_api#metrics_dashboard'
end
+
+ resource :mattermost, only: [:new, :create]
+ resource :variables, only: [:show, :update]
+ resources :triggers, only: [:index, :create, :edit, :update, :destroy]
+
+ resource :mirror, only: [:show, :update] do
+ member do
+ get :ssh_host_keys, constraints: { format: :json }
+ post :update_now
+ end
+ end
+
+ resource :cycle_analytics, only: [:show]
+
+ namespace :cycle_analytics do
+ scope :events, controller: 'events' do
+ get :issue
+ get :plan
+ get :code
+ get :test
+ get :review
+ get :staging
+ get :production
+ end
+ end
end
# End of the /-/ scope.
@@ -235,8 +260,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resource :mattermost, only: [:new, :create]
-
namespace :prometheus do
resources :metrics, constraints: { id: %r{[^\/]+} }, only: [:index, :new, :create, :edit, :update, :destroy] do
get :active_common, on: :collection
@@ -364,17 +387,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
put '/service_desk' => 'service_desk#update', as: :service_desk_refresh
end
- resource :variables, only: [:show, :update]
-
- resources :triggers, only: [:index, :create, :edit, :update, :destroy]
-
- resource :mirror, only: [:show, :update] do
- member do
- get :ssh_host_keys, constraints: { format: :json }
- post :update_now
- end
- end
-
Gitlab.ee do
resources :push_rules, constraints: { id: /\d+/ }, only: [:update]
end
@@ -463,20 +475,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resource :cycle_analytics, only: [:show]
-
- namespace :cycle_analytics do
- scope :events, controller: 'events' do
- get :issue
- get :plan
- get :code
- get :test
- get :review
- get :staging
- get :production
- end
- end
-
namespace :serverless do
scope :functions do
get '/:environment_id/:id', to: 'functions#show'
@@ -678,7 +676,8 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
:network, :graphs, :autocomplete_sources,
:project_members, :deploy_keys, :deploy_tokens,
:labels, :milestones, :services, :boards, :releases,
- :forks, :group_links, :import, :avatar)
+ :forks, :group_links, :import, :avatar, :mirror,
+ :cycle_analytics, :mattermost, :variables, :triggers)
end
end
end
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 0e397ed4f78..c75d9f236f9 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -139,7 +139,22 @@ type Commit {
"""
Latest pipeline of the commit
"""
- latestPipeline: Pipeline
+ latestPipeline(
+ """
+ Filter pipelines by the ref they are run for
+ """
+ ref: String
+
+ """
+ Filter pipelines by the sha of the commit they are run for
+ """
+ sha: String
+
+ """
+ Filter pipelines by their status
+ """
+ status: PipelineStatusEnum
+ ): Pipeline @deprecated(reason: "use pipelines")
"""
Raw commit message
@@ -147,6 +162,46 @@ type Commit {
message: String
"""
+ Pipelines of the commit ordered latest first
+ """
+ pipelines(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+
+ """
+ Filter pipelines by the ref they are run for
+ """
+ ref: String
+
+ """
+ Filter pipelines by the sha of the commit they are run for
+ """
+ sha: String
+
+ """
+ Filter pipelines by their status
+ """
+ status: PipelineStatusEnum
+ ): PipelineConnection
+
+ """
SHA1 ID of the commit
"""
sha: String!
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 384f641af62..629c18629cf 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -10210,15 +10210,44 @@
"name": "latestPipeline",
"description": "Latest pipeline of the commit",
"args": [
-
+ {
+ "name": "status",
+ "description": "Filter pipelines by their status",
+ "type": {
+ "kind": "ENUM",
+ "name": "PipelineStatusEnum",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "ref",
+ "description": "Filter pipelines by the ref they are run for",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "sha",
+ "description": "Filter pipelines by the sha of the commit they are run for",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
],
"type": {
"kind": "OBJECT",
"name": "Pipeline",
"ofType": null
},
- "isDeprecated": false,
- "deprecationReason": null
+ "isDeprecated": true,
+ "deprecationReason": "use pipelines"
},
{
"name": "message",
@@ -10235,6 +10264,89 @@
"deprecationReason": null
},
{
+ "name": "pipelines",
+ "description": "Pipelines of the commit ordered latest first",
+ "args": [
+ {
+ "name": "status",
+ "description": "Filter pipelines by their status",
+ "type": {
+ "kind": "ENUM",
+ "name": "PipelineStatusEnum",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "ref",
+ "description": "Filter pipelines by the ref they are run for",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "sha",
+ "description": "Filter pipelines by the sha of the commit they are run for",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "PipelineConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "sha",
"description": "SHA1 ID of the commit",
"args": [
@@ -10308,6 +10420,118 @@
},
{
"kind": "OBJECT",
+ "name": "PipelineConnection",
+ "description": "The connection type for Pipeline.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PipelineEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "Pipeline",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "PipelineEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "Pipeline",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "Pipeline",
"description": null,
"fields": [
@@ -13207,118 +13431,6 @@
},
{
"kind": "OBJECT",
- "name": "PipelineConnection",
- "description": "The connection type for Pipeline.",
- "fields": [
- {
- "name": "edges",
- "description": "A list of edges.",
- "args": [
-
- ],
- "type": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "OBJECT",
- "name": "PipelineEdge",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "nodes",
- "description": "A list of nodes.",
- "args": [
-
- ],
- "type": {
- "kind": "LIST",
- "name": null,
- "ofType": {
- "kind": "OBJECT",
- "name": "Pipeline",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "pageInfo",
- "description": "Information to aid in pagination.",
- "args": [
-
- ],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "OBJECT",
- "name": "PageInfo",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- }
- ],
- "inputFields": null,
- "interfaces": [
-
- ],
- "enumValues": null,
- "possibleTypes": null
- },
- {
- "kind": "OBJECT",
- "name": "PipelineEdge",
- "description": "An edge in a connection.",
- "fields": [
- {
- "name": "cursor",
- "description": "A cursor for use in pagination.",
- "args": [
-
- ],
- "type": {
- "kind": "NON_NULL",
- "name": null,
- "ofType": {
- "kind": "SCALAR",
- "name": "String",
- "ofType": null
- }
- },
- "isDeprecated": false,
- "deprecationReason": null
- },
- {
- "name": "node",
- "description": "The item at the end of the edge.",
- "args": [
-
- ],
- "type": {
- "kind": "OBJECT",
- "name": "Pipeline",
- "ofType": null
- },
- "isDeprecated": false,
- "deprecationReason": null
- }
- ],
- "inputFields": null,
- "interfaces": [
-
- ],
- "enumValues": null,
- "possibleTypes": null
- },
- {
- "kind": "OBJECT",
"name": "IssueConnection",
"description": "The connection type for Issue.",
"fields": [
diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md
index c5aae41b587..931755c6305 100644
--- a/doc/user/application_security/container_scanning/index.md
+++ b/doc/user/application_security/container_scanning/index.md
@@ -185,19 +185,40 @@ Container Scanning can be executed on an offline air-gapped GitLab Ultimate inst
1. Host the following Docker images on a [local Docker container registry](../../packages/container_registry/index.md):
- [arminc/clair-db vulnerabilities database](https://hub.docker.com/r/arminc/clair-db)
- [GitLab klar analyzer](https://gitlab.com/gitlab-org/security-products/analyzers/klar)
-1. [Override the container scanning template](#overriding-the-container-scanning-template) in your `.gitlab-ci.yml` file to refer to the Docker
- images hosted on your local Docker container registry:
+1. [Override the container scanning template](#overriding-the-container-scanning-template) in your `.gitlab-ci.yml` file to refer to the Docker images hosted on your local Docker container registry:
```yaml
include:
- template: Container-Scanning.gitlab-ci.yml
container_scanning:
- image: your.local.registry:5000/gitlab-klar-analyzer
+ image: $CI_REGISTRY/namespace/gitlab-klar-analyzer
variables:
- CLAIR_DB_IMAGE: your.local.registry:5000/clair-vulnerabilities-db
+ CLAIR_DB_IMAGE: $CI_REGISTRY/namespace/clair-vulnerabilities-db
```
+It may be worthwhile to set up a [scheduled pipeline](../../project/pipelines/schedules.md) to automatically build a new version of the vulnerabilities database on a preset schedule. You can use the following `.gitlab-yml.ci` as a template:
+
+```yaml
+image: docker:stable
+
+services:
+ - docker:stable-dind
+
+stages:
+ - build
+
+build_latest_vulnerabilities:
+ stage: build
+ script:
+ - docker pull arminc/clair-db:latest
+ - docker tag arminc/clair-db:latest $CI_REGISTRY/namespace/clair-vulnerabilities-db
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+ - docker push $CI_REGISTRY/namespace/clair-vulnerabilities-db
+```
+
+The above template will work for a GitLab Docker registry running on a local installation, however, if you're using a non-GitLab Docker registry, you'll need to change the `$CI_REGISTRY` value and the `docker login` credentials to match the details of your local registry.
+
## Troubleshooting
### docker: Error response from daemon: failed to copy xattrs
diff --git a/doc/user/project/clusters/serverless/index.md b/doc/user/project/clusters/serverless/index.md
index be6371bb1ca..26db2133d09 100644
--- a/doc/user/project/clusters/serverless/index.md
+++ b/doc/user/project/clusters/serverless/index.md
@@ -313,6 +313,41 @@ The sample function can now be triggered from any HTTP client using a simple `PO
![function execution](img/function-execution.png)
+### Running functions locally
+
+Running a function locally is a good way to quickly verify behavior during development.
+
+Running functions locally requires:
+
+- Go 1.12 or newer installed.
+- Docker Engine installed and running.
+- `gitlabktl` installed using the Go package manager:
+
+ ```shell
+ GO111MODULE=on go get gitlab.com/gitlab-org/gitlabktl
+ ```
+
+To run a function locally:
+
+1. Navigate to the root of your GitLab serverless project.
+1. Build your function into a Docker image:
+
+ ```shell
+ gitlabktl serverless build
+ ```
+
+1. Run your function in Docker:
+
+ ```shell
+ docker run -itp 8080:8080 <your_function_name>
+ ```
+
+1. Invoke your function:
+
+ ```shell
+ curl http://localhost:8080
+ ```
+
## Deploying Serverless applications
> Introduced in GitLab 11.5.
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 0062759d993..a2bdb76b834 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -113,6 +113,7 @@ module API
mount ::API::Files
mount ::API::GroupBoards
mount ::API::GroupClusters
+ mount ::API::GroupExport
mount ::API::GroupLabels
mount ::API::GroupMilestones
mount ::API::Groups
diff --git a/lib/api/group_export.rb b/lib/api/group_export.rb
new file mode 100644
index 00000000000..8025a16e191
--- /dev/null
+++ b/lib/api/group_export.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module API
+ class GroupExport < Grape::API
+ before do
+ authorize! :admin_group, user_group
+ end
+
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups, requirements: { id: %r{[^/]+} } do
+ desc 'Download export' do
+ detail 'This feature was introduced in GitLab 12.5.'
+ end
+ get ':id/export/download' do
+ if user_group.export_file_exists?
+ present_carrierwave_file!(user_group.export_file)
+ else
+ render_api_error!('404 Not found or has expired', 404)
+ end
+ end
+
+ desc 'Start export' do
+ detail 'This feature was introduced in GitLab 12.5.'
+ end
+ post ':id/export' do
+ GroupExportWorker.perform_async(current_user.id, user_group.id, params)
+
+ accepted!
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
index 3cdb7b5420c..a60b00b2ee8 100644
--- a/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Jobs/Code-Quality.gitlab-ci.yml
@@ -18,7 +18,7 @@ code_quality:
--env SOURCE_CODE="$PWD"
--volume "$PWD":/code
--volume /var/run/docker.sock:/var/run/docker.sock
- "registry.gitlab.com/gitlab-org/security-products/codequality:12-0-stable" /code
+ "registry.gitlab.com/gitlab-org/security-products/codequality:12-5-stable" /code
artifacts:
reports:
codequality: gl-code-quality-report.json
diff --git a/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb b/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb
deleted file mode 100644
index 70344392138..00000000000
--- a/lib/gitlab/graphql/loaders/pipeline_for_sha_loader.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Graphql
- module Loaders
- class PipelineForShaLoader
- attr_accessor :project, :sha
-
- def initialize(project, sha)
- @project, @sha = project, sha
- end
-
- def find_last
- BatchLoader::GraphQL.for(sha).batch(key: project) do |shas, loader, args|
- pipelines = args[:key].ci_pipelines.latest_for_shas(shas)
-
- pipelines.each do |pipeline|
- loader.call(pipeline.sha, pipeline)
- end
- end
- end
- end
- end
- end
-end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c8ec6da91ec..bcb56879f14 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -5000,7 +5000,7 @@ msgstr ""
msgid "CurrentUser|Settings"
msgstr ""
-msgid "CurrentUser|Start a trial"
+msgid "CurrentUser|Start a Gold trial"
msgstr ""
msgid "Custom CI configuration path"
@@ -7306,9 +7306,6 @@ msgstr ""
msgid "FeatureFlags|Inactive flag for %{scope}"
msgstr ""
-msgid "FeatureFlags|Include additional user IDs"
-msgstr ""
-
msgid "FeatureFlags|Install a %{docs_link_anchored_start}compatible client library%{docs_link_anchored_end} and specify the API URL, application name, and instance ID during the configuration setup. %{docs_link_start}More Information%{docs_link_end}"
msgstr ""
diff --git a/spec/features/projects/files/user_reads_pipeline_status_spec.rb b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
index 15f8fa7438d..9d38c44b6ef 100644
--- a/spec/features/projects/files/user_reads_pipeline_status_spec.rb
+++ b/spec/features/projects/files/user_reads_pipeline_status_spec.rb
@@ -9,8 +9,6 @@ describe 'user reads pipeline status', :js do
let(:x110_pipeline) { create_pipeline('x1.1.0', 'failed') }
before do
- stub_feature_flags(vue_file_list: false)
-
project.add_maintainer(user)
project.repository.add_tag(user, 'x1.1.0', 'v1.1.0')
@@ -25,7 +23,7 @@ describe 'user reads pipeline status', :js do
visit project_tree_path(project, expected_pipeline.ref)
wait_for_requests
- page.within('.blob-commit-info') do
+ page.within('.commit-detail') do
expect(page).to have_link('', href: project_pipeline_path(project, expected_pipeline))
expect(page).to have_selector(".ci-status-icon-#{expected_pipeline.status}")
end
diff --git a/spec/frontend/cycle_analytics/stage_nav_item_spec.js b/spec/frontend/cycle_analytics/stage_nav_item_spec.js
index ff079082ca7..a7a1d563e1e 100644
--- a/spec/frontend/cycle_analytics/stage_nav_item_spec.js
+++ b/spec/frontend/cycle_analytics/stage_nav_item_spec.js
@@ -133,45 +133,19 @@ describe('StageNavItem', () => {
hasStageName();
});
- it('renders options menu', () => {
- expect(wrapper.find('.more-actions-toggle').exists()).toBe(true);
+ it('does not render options menu', () => {
+ expect(wrapper.find('.more-actions-toggle').exists()).toBe(false);
});
- describe('Default stages', () => {
- beforeEach(() => {
- wrapper = createComponent(
- { canEdit: true, isUserAllowed: true, isDefaultStage: true },
- false,
- );
- });
- it('can hide the stage', () => {
- expect(wrapper.text()).toContain('Hide stage');
- });
- it('can not edit the stage', () => {
- expect(wrapper.text()).not.toContain('Edit stage');
- });
- it('can not remove the stage', () => {
- expect(wrapper.text()).not.toContain('Remove stage');
- });
+ it('can not edit the stage', () => {
+ expect(wrapper.text()).not.toContain('Edit stage');
+ });
+ it('can not remove the stage', () => {
+ expect(wrapper.text()).not.toContain('Remove stage');
});
- describe('Custom stages', () => {
- beforeEach(() => {
- wrapper = createComponent(
- { canEdit: true, isUserAllowed: true, isDefaultStage: false },
- false,
- );
- });
- it('can edit the stage', () => {
- expect(wrapper.text()).toContain('Edit stage');
- });
- it('can remove the stage', () => {
- expect(wrapper.text()).toContain('Remove stage');
- });
-
- it('can not hide the stage', () => {
- expect(wrapper.text()).not.toContain('Hide stage');
- });
+ it('can not hide the stage', () => {
+ expect(wrapper.text()).not.toContain('Hide stage');
});
});
});
diff --git a/spec/frontend/repository/components/last_commit_spec.js b/spec/frontend/repository/components/last_commit_spec.js
index 01b56d453e6..e07ad4cf46b 100644
--- a/spec/frontend/repository/components/last_commit_spec.js
+++ b/spec/frontend/repository/components/last_commit_spec.js
@@ -17,7 +17,7 @@ function createCommitData(data = {}) {
avatarUrl: 'https://test.com',
webUrl: 'https://test.com/test',
},
- latestPipeline: {
+ pipeline: {
detailedStatus: {
detailsPath: 'https://test.com/pipeline',
icon: 'failed',
@@ -74,7 +74,7 @@ describe('Repository last commit component', () => {
});
it('hides pipeline components when pipeline does not exist', () => {
- factory(createCommitData({ latestPipeline: null }));
+ factory(createCommitData({ pipeline: null }));
expect(vm.find('.js-commit-pipeline').exists()).toBe(false);
});
diff --git a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
index 3d5ec3fd411..a5e3eb4bce1 100644
--- a/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
+++ b/spec/frontend/repository/components/preview/__snapshots__/index_spec.js.snap
@@ -2,7 +2,7 @@
exports[`Repository file preview component renders file HTML 1`] = `
<article
- class="file-holder js-hide-on-navigation limited-width-container readme-holder"
+ class="file-holder limited-width-container readme-holder"
>
<div
class="file-title"
diff --git a/spec/frontend/repository/components/tree_content_spec.js b/spec/frontend/repository/components/tree_content_spec.js
index 954c4791c04..148e307a5d4 100644
--- a/spec/frontend/repository/components/tree_content_spec.js
+++ b/spec/frontend/repository/components/tree_content_spec.js
@@ -28,7 +28,7 @@ describe('Repository table component', () => {
it('renders file preview', () => {
factory('/');
- vm.setData({ entries: { blobs: [{ name: 'README.md ' }] } });
+ vm.setData({ entries: { blobs: [{ name: 'README.md' }] } });
expect(vm.find(FilePreview).exists()).toBe(true);
});
diff --git a/spec/frontend/repository/utils/readme_spec.js b/spec/frontend/repository/utils/readme_spec.js
new file mode 100644
index 00000000000..6b7876c8947
--- /dev/null
+++ b/spec/frontend/repository/utils/readme_spec.js
@@ -0,0 +1,33 @@
+import { readmeFile } from '~/repository/utils/readme';
+
+describe('readmeFile', () => {
+ describe('markdown files', () => {
+ it('returns markdown file', () => {
+ expect(readmeFile([{ name: 'README' }, { name: 'README.md' }])).toEqual({
+ name: 'README.md',
+ });
+
+ expect(readmeFile([{ name: 'README' }, { name: 'index.md' }])).toEqual({
+ name: 'index.md',
+ });
+ });
+ });
+
+ describe('plain files', () => {
+ it('returns plain file', () => {
+ expect(readmeFile([{ name: 'README' }, { name: 'TEST.md' }])).toEqual({
+ name: 'README',
+ });
+
+ expect(readmeFile([{ name: 'readme' }, { name: 'TEST.md' }])).toEqual({
+ name: 'readme',
+ });
+ });
+ });
+
+ describe('non-previewable file', () => {
+ it('returns undefined', () => {
+ expect(readmeFile([{ name: 'index.js' }, { name: 'TEST.md' }])).toBe(undefined);
+ });
+ });
+});
diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb
index c162fdbbb47..a212bd07f35 100644
--- a/spec/graphql/resolvers/base_resolver_spec.rb
+++ b/spec/graphql/resolvers/base_resolver_spec.rb
@@ -13,6 +13,14 @@ describe Resolvers::BaseResolver do
end
end
+ let(:last_resolver) do
+ Class.new(described_class) do
+ def resolve(**args)
+ [1, 2]
+ end
+ end
+ end
+
describe '.single' do
it 'returns a subclass from the resolver' do
expect(resolver.single.superclass).to eq(resolver)
@@ -29,6 +37,22 @@ describe Resolvers::BaseResolver do
end
end
+ describe '.last' do
+ it 'returns a subclass from the resolver' do
+ expect(last_resolver.last.superclass).to eq(last_resolver)
+ end
+
+ it 'returns the same subclass every time' do
+ expect(last_resolver.last.object_id).to eq(last_resolver.last.object_id)
+ end
+
+ it 'returns a resolver that gives the last result from the original resolver' do
+ result = resolve(last_resolver.last)
+
+ expect(result).to eq(2)
+ end
+ end
+
context 'when field is a connection' do
it 'increases complexity based on arguments' do
field = Types::BaseField.new(name: 'test', type: GraphQL::STRING_TYPE.connection_type, resolver_class: described_class, null: false, max_page_size: 1)
diff --git a/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb b/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
new file mode 100644
index 00000000000..93da877d714
--- /dev/null
+++ b/spec/graphql/resolvers/commit_pipelines_resolver_spec.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Resolvers::CommitPipelinesResolver do
+ include GraphqlHelpers
+
+ let_it_be(:project) { create(:project) }
+ let(:commit) { create(:commit, project: project) }
+ let_it_be(:current_user) { create(:user) }
+
+ let!(:pipeline) do
+ create(
+ :ci_pipeline,
+ project: project,
+ sha: commit.id,
+ ref: 'master',
+ status: 'success'
+ )
+ end
+ let!(:pipeline2) do
+ create(
+ :ci_pipeline,
+ project: project,
+ sha: commit.id,
+ ref: 'master',
+ status: 'failed'
+ )
+ end
+ let!(:pipeline3) do
+ create(
+ :ci_pipeline,
+ project: project,
+ sha: commit.id,
+ ref: 'my_branch',
+ status: 'failed'
+ )
+ end
+
+ before do
+ commit.project.add_developer(current_user)
+ end
+
+ def resolve_pipelines
+ resolve(described_class, obj: commit, ctx: { current_user: current_user }, args: { ref: 'master' })
+ end
+
+ it 'resolves pipelines for commit and ref' do
+ pipelines = resolve_pipelines
+
+ expect(pipelines).to eq([pipeline2, pipeline])
+ end
+end
diff --git a/spec/graphql/types/commit_type_spec.rb b/spec/graphql/types/commit_type_spec.rb
index 1ff1c97f8db..ee9af886e60 100644
--- a/spec/graphql/types/commit_type_spec.rb
+++ b/spec/graphql/types/commit_type_spec.rb
@@ -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, :signature_html
+ :author, :web_url, :latest_pipeline, :pipelines, :signature_html
)
end
end
diff --git a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
index b2fe315f6c6..b53e30b6896 100644
--- a/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
+++ b/spec/javascripts/ci_variable_list/ajax_variable_list_spec.js
@@ -3,7 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import AjaxFormVariableList from '~/ci_variable_list/ajax_variable_list';
-const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-project/variables';
+const VARIABLE_PATCH_ENDPOINT = 'http://test.host/frontend-fixtures/builds-project/-/variables';
const HIDE_CLASS = 'hide';
describe('AjaxFormVariableList', () => {
diff --git a/spec/lib/gitlab/fogbugz_import/client_spec.rb b/spec/lib/gitlab/fogbugz_import/client_spec.rb
index dcd1a2d9813..676511211c8 100644
--- a/spec/lib/gitlab/fogbugz_import/client_spec.rb
+++ b/spec/lib/gitlab/fogbugz_import/client_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::FogbugzImport::Client do
diff --git a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb b/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb
deleted file mode 100644
index 136027736c3..00000000000
--- a/spec/lib/gitlab/graphql/loaders/pipeline_for_sha_loader_spec.rb
+++ /dev/null
@@ -1,20 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Graphql::Loaders::PipelineForShaLoader do
- include GraphqlHelpers
-
- describe '#find_last' do
- it 'batch-resolves latest pipeline' do
- project = create(:project, :repository)
- pipeline1 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
- pipeline2 = create(:ci_pipeline, project: project, ref: project.default_branch, sha: project.commit.sha)
- pipeline3 = create(:ci_pipeline, project: project, ref: 'improve/awesome', sha: project.commit('improve/awesome').sha)
-
- result = batch_sync(max_queries: 1) do
- [pipeline1.sha, pipeline3.sha].map { |sha| described_class.new(project, sha).find_last }
- end
-
- expect(result).to contain_exactly(pipeline2, pipeline3)
- end
- end
-end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index b86663fd7d9..0f7f68e0b38 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -378,6 +378,14 @@ describe MergeRequestDiff do
expect(diff_with_commits.commit_shas).not_to be_empty
expect(diff_with_commits.commit_shas).to all(match(/\h{40}/))
end
+
+ context 'with limit attribute' do
+ it 'returns limited number of shas' do
+ expect(diff_with_commits.commit_shas(limit: 2).size).to eq(2)
+ expect(diff_with_commits.commit_shas(limit: 100).size).to eq(29)
+ expect(diff_with_commits.commit_shas.size).to eq(29)
+ end
+ end
end
describe '#compare_with' do
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index f775dfb87a2..b19f7a80d63 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -1261,13 +1261,49 @@ describe MergeRequest do
end
describe '#commit_shas' do
- before do
- allow(subject.merge_request_diff).to receive(:commit_shas)
- .and_return(['sha1'])
+ context 'persisted merge request' do
+ context 'with a limit' do
+ it 'returns a limited number of commit shas' do
+ expect(subject.commit_shas(limit: 2)).to eq(%w[
+ b83d6e391c22777fca1ed3012fce84f633d7fed0 498214de67004b1da3d820901307bed2a68a8ef6
+ ])
+ end
+ end
+
+ context 'without a limit' do
+ it 'returns all commit shas of the merge request diff' do
+ expect(subject.commit_shas.size).to eq(29)
+ end
+ end
end
- it 'delegates to merge request diff' do
- expect(subject.commit_shas).to eq ['sha1']
+ context 'new merge request' do
+ subject { build(:merge_request) }
+
+ context 'compare commits' do
+ before do
+ subject.compare_commits = [
+ double(sha: 'sha1'), double(sha: 'sha2')
+ ]
+ end
+
+ context 'without a limit' do
+ it 'returns all shas of compare commits' do
+ expect(subject.commit_shas).to eq(%w[sha2 sha1])
+ end
+ end
+
+ context 'with a limit' do
+ it 'returns a limited number of shas' do
+ expect(subject.commit_shas(limit: 1)).to eq(['sha2'])
+ end
+ end
+ end
+
+ it 'returns diff_head_sha as an array' do
+ expect(subject.commit_shas).to eq([subject.diff_head_sha])
+ expect(subject.commit_shas(limit: 2)).to eq([subject.diff_head_sha])
+ end
end
end
diff --git a/spec/requests/api/group_export_spec.rb b/spec/requests/api/group_export_spec.rb
new file mode 100644
index 00000000000..ac4853e5388
--- /dev/null
+++ b/spec/requests/api/group_export_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::GroupExport do
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+
+ let(:path) { "/groups/#{group.id}/export" }
+ let(:download_path) { "/groups/#{group.id}/export/download" }
+
+ let(:export_path) { "#{Dir.tmpdir}/group_export_spec" }
+
+ before do
+ allow_next_instance_of(Gitlab::ImportExport) do |import_export|
+ expect(import_export).to receive(:storage_path).and_return(export_path)
+ end
+ end
+
+ after do
+ FileUtils.rm_rf(export_path, secure: true)
+ end
+
+ describe 'GET /groups/:group_id/export/download' do
+ let(:upload) { ImportExportUpload.new(group: group) }
+
+ before do
+ stub_uploads_object_storage(ImportExportUploader)
+
+ group.add_owner(user)
+ end
+
+ context 'when export file exists' do
+ before do
+ upload.export_file = fixture_file_upload('spec/fixtures/group_export.tar.gz', "`/tar.gz")
+ upload.save!
+ end
+
+ it 'downloads exported group archive' do
+ get api(download_path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ context 'when export_file.file does not exist' do
+ before do
+ expect_next_instance_of(ImportExportUploader) do |uploader|
+ expect(uploader).to receive(:file).and_return(nil)
+ end
+ end
+
+ it 'returns 404' do
+ get api(download_path, user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ context 'when export file does not exist' do
+ it 'returns 404' do
+ get api(download_path, user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ describe 'POST /groups/:group_id/export' do
+ context 'when user is a group owner' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'accepts download' do
+ post api(path, user)
+
+ expect(response).to have_gitlab_http_status(202)
+ end
+ end
+
+ context 'when user is not a group owner' do
+ before do
+ group.add_developer(user)
+ end
+
+ it 'forbids the request' do
+ post api(path, user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/access_matchers_helpers.rb b/spec/support/helpers/access_matchers_helpers.rb
new file mode 100644
index 00000000000..9100f245d36
--- /dev/null
+++ b/spec/support/helpers/access_matchers_helpers.rb
@@ -0,0 +1,95 @@
+# frozen_string_literal: true
+
+module AccessMatchersHelpers
+ USER_ACCESSOR_METHOD_NAME = 'user'
+
+ def provide_user(role, membership = nil)
+ case role
+ when :admin
+ create(:admin)
+ when :auditor
+ create(:user, :auditor)
+ when :user
+ create(:user)
+ when :external
+ create(:user, :external)
+ when :visitor, :anonymous
+ nil
+ when User
+ role
+ when *Gitlab::Access.sym_options_with_owner.keys # owner, maintainer, developer, reporter, guest
+ raise ArgumentError, "cannot provide #{role} when membership reference is blank" unless membership
+
+ provide_user_by_membership(role, membership)
+ else
+ raise ArgumentError, "cannot provide user of an unknown role #{role}"
+ end
+ end
+
+ def provide_user_by_membership(role, membership)
+ if role == :owner && membership.owner
+ membership.owner
+ else
+ create(:user).tap do |user|
+ membership.public_send(:"add_#{role}", user)
+ end
+ end
+ end
+
+ def raise_if_non_block_expectation!(actual)
+ raise ArgumentError, 'This matcher supports block expectations only.' unless actual.is_a?(Proc)
+ end
+
+ def update_owner(objects, user)
+ return unless objects
+
+ objects.each do |object|
+ if object.respond_to?(:owner)
+ object.update_attribute(:owner, user)
+ elsif object.respond_to?(:user)
+ object.update_attribute(:user, user)
+ else
+ raise ArgumentError, "cannot own this object #{object}"
+ end
+ end
+ end
+
+ def patch_example_group(user)
+ return if user.nil? # for anonymous users
+
+ # This call is evaluated in context of ExampleGroup instance in which the matcher is called. Overrides the `user`
+ # (or defined by `method_name`) method generated by `let` definition in example group before it's used by `subject`.
+ # This override is per concrete example only because the example group class gets re-created for each example.
+ instance_eval(<<~CODE, __FILE__, __LINE__ + 1)
+ if instance_variable_get(:@__#{USER_ACCESSOR_METHOD_NAME}_patched)
+ raise ArgumentError, 'An access matcher be_allowed_for/be_denied_for can be used only once per example (`it` block)'
+ end
+ instance_variable_set(:@__#{USER_ACCESSOR_METHOD_NAME}_patched, true)
+
+ def #{USER_ACCESSOR_METHOD_NAME}
+ @#{USER_ACCESSOR_METHOD_NAME} ||= User.find(#{user.id})
+ end
+ CODE
+ end
+
+ def prepare_matcher_environment(role, membership, owned_objects)
+ user = provide_user(role, membership)
+
+ if user
+ update_owner(owned_objects, user)
+ patch_example_group(user)
+ end
+ end
+
+ def run_matcher(action, role, membership, owned_objects)
+ raise_if_non_block_expectation!(action)
+
+ prepare_matcher_environment(role, membership, owned_objects)
+
+ if block_given?
+ yield action
+ else
+ action.call
+ end
+ end
+end
diff --git a/spec/support/matchers/access_matchers_for_request.rb b/spec/support/matchers/access_matchers_for_request.rb
new file mode 100644
index 00000000000..9b80bf8562c
--- /dev/null
+++ b/spec/support/matchers/access_matchers_for_request.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+# AccessMatchersForRequest
+#
+# Matchers to test the access permissions for requests specs (most useful for API tests).
+module AccessMatchersForRequest
+ extend RSpec::Matchers::DSL
+ include AccessMatchersHelpers
+
+ EXPECTED_STATUS_CODES_ALLOWED = [200, 201, 204, 302, 304].freeze
+ EXPECTED_STATUS_CODES_DENIED = [401, 403, 404].freeze
+
+ def description_for(role, type, expected, result)
+ "be #{type} for #{role} role. Expected status code: any of #{expected.join(', ')} Got: #{result}"
+ end
+
+ matcher :be_allowed_for do |role|
+ match do |action|
+ # methods called in this and negated block are being run in context of ExampleGroup
+ # (not matcher) instance so we have to pass data via local vars
+
+ run_matcher(action, role, @membership, @owned_objects)
+
+ EXPECTED_STATUS_CODES_ALLOWED.include?(response.status)
+ end
+
+ match_when_negated do |action|
+ run_matcher(action, role, @membership, @owned_objects)
+
+ EXPECTED_STATUS_CODES_DENIED.include?(response.status)
+ end
+
+ chain :of do |membership|
+ @membership = membership
+ end
+
+ chain :own do |*owned_objects|
+ @owned_objects = owned_objects
+ end
+
+ failure_message do
+ "expected this action to #{description_for(role, 'allowed', EXPECTED_STATUS_CODES_ALLOWED, response.status)}"
+ end
+
+ failure_message_when_negated do
+ "expected this action to #{description_for(role, 'denied', EXPECTED_STATUS_CODES_DENIED, response.status)}"
+ end
+
+ supports_block_expectations
+ end
+
+ RSpec::Matchers.define_negated_matcher :be_denied_for, :be_allowed_for
+end
diff --git a/spec/support/matchers/access_matchers_generic.rb b/spec/support/matchers/access_matchers_generic.rb
new file mode 100644
index 00000000000..13955750f4f
--- /dev/null
+++ b/spec/support/matchers/access_matchers_generic.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+# AccessMatchersGeneric
+#
+# Matchers to test the access permissions for service classes or other generic pieces of business logic.
+module AccessMatchersGeneric
+ extend RSpec::Matchers::DSL
+ include AccessMatchersHelpers
+
+ ERROR_CLASS = Gitlab::Access::AccessDeniedError
+
+ def error_message(error)
+ str = error.class.name
+ str += ": #{error.message}" if error.message != error.class.name
+ str
+ end
+
+ def error_expectation_message(allowed, error)
+ if allowed
+ "Expected to raise nothing but #{error_message(error)} was raised."
+ else
+ "Expected to raise #{ERROR_CLASS} but nothing was raised."
+ end
+ end
+
+ def description_for(role, type, error)
+ allowed = type == 'allowed'
+ "be #{type} for #{role} role. #{error_expectation_message(allowed, error)}"
+ end
+
+ matcher :be_allowed_for do |role|
+ match do |action|
+ # methods called in this and negated block are being run in context of ExampleGroup
+ # (not matcher) instance so we have to pass data via local vars
+
+ run_matcher(action, role, @membership, @owned_objects) do |action|
+ action.call
+ rescue => e
+ @error = e
+ raise unless e.is_a?(ERROR_CLASS)
+ end
+
+ @error.nil?
+ end
+
+ chain :of do |membership|
+ @membership = membership
+ end
+
+ chain :own do |*owned_objects|
+ @owned_objects = owned_objects
+ end
+
+ failure_message do
+ "expected this action to #{description_for(role, 'allowed', @error)}"
+ end
+
+ failure_message_when_negated do
+ "expected this action to #{description_for(role, 'denied', @error)}"
+ end
+
+ supports_block_expectations
+ end
+
+ RSpec::Matchers.define_negated_matcher :be_denied_for, :be_allowed_for
+end