summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue3
-rw-r--r--app/assets/javascripts/boards/mixins/modal_footer.js1
-rw-r--r--app/assets/javascripts/issue_show/components/app.vue7
-rw-r--r--app/assets/javascripts/issue_show/components/pinned_links.vue31
-rw-r--r--app/assets/javascripts/labels_select.js3
-rw-r--r--app/assets/javascripts/repository/components/table/index.vue1
-rw-r--r--app/assets/javascripts/repository/components/table/row.vue7
-rw-r--r--app/assets/javascripts/repository/queries/getFiles.query.graphql2
-rw-r--r--app/assets/stylesheets/pages/labels.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss2
-rw-r--r--app/graphql/types/tree/submodule_type.rb3
-rw-r--r--app/graphql/types/tree/tree_type.rb4
-rw-r--r--app/helpers/issuables_helper.rb4
-rw-r--r--app/helpers/submodule_helper.rb12
-rw-r--r--app/models/active_session.rb12
-rw-r--r--app/models/project_feature.rb2
-rw-r--r--app/serializers/diff_file_base_entity.rb11
-rw-r--r--app/serializers/diffs_entity.rb5
-rw-r--r--app/serializers/discussion_serializer.rb14
-rw-r--r--app/serializers/submodule_entity.rb25
-rw-r--r--app/views/devise/shared/_signup_box.html.haml14
-rw-r--r--app/views/doorkeeper/authorizations/new.html.haml2
-rw-r--r--app/views/projects/pages_domains/_form.html.haml2
-rw-r--r--changelogs/unreleased/48771-label-picker-line-break-on-long-label-titles.yml5
-rw-r--r--changelogs/unreleased/64315-mget_sessions_in_chunks.yml5
-rw-r--r--changelogs/unreleased/button-bug-fixes.yml5
-rw-r--r--changelogs/unreleased/issue-zoom-url.yml5
-rw-r--r--changelogs/unreleased/sh-bump-fog-aws.yml5
-rw-r--r--db/post_migrate/20190703185326_fix_wrong_pages_access_level.rb28
-rw-r--r--db/post_migrate/20190715114644_drop_project_features_pages_access_level_default.rb12
-rw-r--r--db/schema.rb4
-rw-r--r--doc/administration/pages/img/lets_encrypt_integration_v12_1.pngbin0 -> 98409 bytes
-rw-r--r--doc/administration/pages/index.md17
-rw-r--r--doc/ci/docker/using_docker_images.md4
-rw-r--r--doc/ci/examples/README.md1
-rw-r--r--doc/ci/yaml/README.md4
-rw-r--r--doc/user/application_security/dependency_scanning/index.md2
-rw-r--r--doc/user/project/autocomplete_characters.md48
-rwxr-xr-xdoc/user/project/img/autocomplete_characters_example1_v12_0.pngbin0 -> 17510 bytes
-rwxr-xr-xdoc/user/project/img/autocomplete_characters_example2_v12_0.pngbin0 -> 14623 bytes
-rw-r--r--doc/user/project/index.md3
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.pngbin0 -> 35040 bytes
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/index.md32
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md68
-rw-r--r--doc/user/project/pages/index.md2
-rw-r--r--doc/user/project/pages/lets_encrypt_for_gitlab_pages.md11
-rw-r--r--doc/user/project/pipelines/settings.md16
-rw-r--r--doc/workflow/time_tracking.md2
-rw-r--r--lib/api/entities.rb1
-rw-r--r--lib/api/helpers/projects_helpers.rb5
-rw-r--r--lib/api/projects.rb4
-rw-r--r--lib/gitlab/background_migration/fix_pages_access_level.rb128
-rw-r--r--lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml2
-rw-r--r--lib/gitlab/git/repository.rb22
-rw-r--r--lib/gitlab/graphql/representation/submodule_tree_entry.rb34
-rw-r--r--lib/gitlab/submodule_links.rb26
-rw-r--r--lib/gitlab/zoom_link_extractor.rb21
-rw-r--r--qa/qa/page/main/oauth.rb4
-rw-r--r--qa/qa/page/main/sign_up.rb26
-rw-r--r--qa/qa/page/project/sub_menus/common.rb6
-rw-r--r--qa/qa/resource/merge_request.rb3
-rw-r--r--spec/features/issues/bulk_assignment_labels_spec.rb2
-rw-r--r--spec/frontend/issue_show/components/pinned_links_spec.js52
-rw-r--r--spec/frontend/repository/components/table/row_spec.js16
-rw-r--r--spec/graphql/types/tree/submodule_type_spec.rb2
-rw-r--r--spec/helpers/issuables_helper_spec.rb41
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb16
-rw-r--r--spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb30
-rw-r--r--spec/lib/gitlab/zoom_link_extractor_spec.rb24
-rw-r--r--spec/migrations/fix_wrong_pages_access_level_spec.rb97
-rw-r--r--spec/models/active_session_spec.rb13
-rw-r--r--spec/models/project_feature_spec.rb28
-rw-r--r--spec/requests/api/projects_spec.rb49
-rw-r--r--spec/serializers/diff_file_base_entity_spec.rb26
-rw-r--r--spec/support/helpers/stub_configuration.rb4
77 files changed, 911 insertions, 227 deletions
diff --git a/Gemfile b/Gemfile
index 7845e9d57fa..8bffc2a973d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -100,7 +100,7 @@ gem 'carrierwave', '~> 1.3'
gem 'mini_magick'
# for backups
-gem 'fog-aws', '~> 3.3'
+gem 'fog-aws', '~> 3.5'
# Locked until fog-google resolves https://github.com/fog/fog-google/issues/421.
# Also see config/initializers/fog_core_patch.rb.
gem 'fog-core', '= 2.1.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index beded888ffd..60939ae918c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -253,7 +253,7 @@ GEM
fog-json
ipaddress (~> 0.8)
xml-simple (~> 1.1)
- fog-aws (3.3.0)
+ fog-aws (3.5.2)
fog-core (~> 2.1)
fog-json (~> 1.1)
fog-xml (~> 0.1)
@@ -1105,7 +1105,7 @@ DEPENDENCIES
flipper-active_support_cache_store (~> 0.13.0)
flowdock (~> 0.7)
fog-aliyun (~> 0.3)
- fog-aws (~> 3.3)
+ fog-aws (~> 3.5)
fog-core (= 2.1.0)
fog-google (~> 1.8)
fog-local (~> 0.6)
diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
index eea0fb71c1a..5f100c617a0 100644
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -1,4 +1,5 @@
<script>
+import footerEEMixin from 'ee_else_ce/boards/mixins/modal_footer';
import Flash from '../../../flash';
import { __, n__ } from '../../../locale';
import ListsDropdown from './lists_dropdown.vue';
@@ -10,7 +11,7 @@ export default {
components: {
ListsDropdown,
},
- mixins: [modalMixin],
+ mixins: [modalMixin, footerEEMixin],
data() {
return {
modal: ModalStore.store,
diff --git a/app/assets/javascripts/boards/mixins/modal_footer.js b/app/assets/javascripts/boards/mixins/modal_footer.js
new file mode 100644
index 00000000000..ff8b4c56321
--- /dev/null
+++ b/app/assets/javascripts/boards/mixins/modal_footer.js
@@ -0,0 +1 @@
+export default {};
diff --git a/app/assets/javascripts/issue_show/components/app.vue b/app/assets/javascripts/issue_show/components/app.vue
index de2a9664cde..9ca38d6bbfa 100644
--- a/app/assets/javascripts/issue_show/components/app.vue
+++ b/app/assets/javascripts/issue_show/components/app.vue
@@ -55,6 +55,11 @@ export default {
required: false,
default: true,
},
+ zoomMeetingUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
issuableRef: {
type: String,
required: true,
@@ -342,7 +347,7 @@ export default {
:title-text="state.titleText"
:show-inline-edit-button="showInlineEditButton"
/>
- <pinned-links :description-html="state.descriptionHtml" />
+ <pinned-links :zoom-meeting-url="zoomMeetingUrl" />
<description-component
v-if="state.descriptionHtml"
:can-update="canUpdate"
diff --git a/app/assets/javascripts/issue_show/components/pinned_links.vue b/app/assets/javascripts/issue_show/components/pinned_links.vue
index 7a54b26bc2b..965e8a3d751 100644
--- a/app/assets/javascripts/issue_show/components/pinned_links.vue
+++ b/app/assets/javascripts/issue_show/components/pinned_links.vue
@@ -8,40 +8,19 @@ export default {
GlLink,
},
props: {
- descriptionHtml: {
+ zoomMeetingUrl: {
type: String,
- required: true,
- },
- },
- computed: {
- linksInDescription() {
- const el = document.createElement('div');
- el.innerHTML = this.descriptionHtml;
- return [...el.querySelectorAll('a')].map(a => a.href);
- },
- // Detect links matching the following formats:
- // Zoom Start links: https://zoom.us/s/<meeting-id>
- // Zoom Join links: https://zoom.us/j/<meeting-id>
- // Personal Zoom links: https://zoom.us/my/<meeting-id>
- // Vanity Zoom links: https://gitlab.zoom.us/j/<meeting-id> (also /s and /my)
- zoomHref() {
- const zoomRegex = /^https:\/\/([\w\d-]+\.)?zoom\.us\/(s|j|my)\/.+/;
- return this.linksInDescription.reduce((acc, currentLink) => {
- let lastLink = acc;
- if (zoomRegex.test(currentLink)) {
- lastLink = currentLink;
- }
- return lastLink;
- }, '');
+ required: false,
+ default: null,
},
},
};
</script>
<template>
- <div v-if="zoomHref" class="border-bottom mb-3 mt-n2">
+ <div v-if="zoomMeetingUrl" class="border-bottom mb-3 mt-n2">
<gl-link
- :href="zoomHref"
+ :href="zoomMeetingUrl"
target="_blank"
class="btn btn-inverted btn-secondary btn-sm text-dark mb-3"
>
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index bea43430edc..f50a6e3b19d 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -311,7 +311,8 @@ export default class LabelsSelect {
// We need to identify which items are actually labels
if (label.id) {
- selectedClass.push('label-item');
+ const selectedLayoutClasses = ['d-flex', 'flex-row', 'text-break-word'];
+ selectedClass.push('label-item', ...selectedLayoutClasses);
linkEl.dataset.labelId = label.id;
}
diff --git a/app/assets/javascripts/repository/components/table/index.vue b/app/assets/javascripts/repository/components/table/index.vue
index 0d9e992e596..610c7e8d99e 100644
--- a/app/assets/javascripts/repository/components/table/index.vue
+++ b/app/assets/javascripts/repository/components/table/index.vue
@@ -137,6 +137,7 @@ export default {
:path="entry.flatPath"
:type="entry.type"
:url="entry.webUrl"
+ :submodule-tree-url="entry.treeUrl"
:lfs-oid="entry.lfsOid"
/>
</template>
diff --git a/app/assets/javascripts/repository/components/table/row.vue b/app/assets/javascripts/repository/components/table/row.vue
index 3e060e9ecb6..6029460d975 100644
--- a/app/assets/javascripts/repository/components/table/row.vue
+++ b/app/assets/javascripts/repository/components/table/row.vue
@@ -62,6 +62,11 @@ export default {
required: false,
default: null,
},
+ submoduleTreeUrl: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
data() {
return {
@@ -112,7 +117,7 @@ export default {
</component>
<gl-badge v-if="lfsOid" variant="default" class="label-lfs ml-1">LFS</gl-badge>
<template v-if="isSubmodule">
- @ <gl-link href="#" class="commit-sha">{{ shortSha }}</gl-link>
+ @ <gl-link :href="submoduleTreeUrl" class="commit-sha">{{ shortSha }}</gl-link>
</template>
</td>
<td class="d-none d-sm-table-cell tree-commit">
diff --git a/app/assets/javascripts/repository/queries/getFiles.query.graphql b/app/assets/javascripts/repository/queries/getFiles.query.graphql
index 4c24fc4087f..b3cc0878cad 100644
--- a/app/assets/javascripts/repository/queries/getFiles.query.graphql
+++ b/app/assets/javascripts/repository/queries/getFiles.query.graphql
@@ -35,6 +35,8 @@ query getFiles(
edges {
node {
...TreeEntry
+ webUrl
+ treeUrl
}
}
pageInfo {
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 11e8a32389f..7d5e185834b 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -30,6 +30,10 @@
.dropdown-content {
max-height: 135px;
}
+
+ .dropdown-label-box {
+ flex: 0 0 auto;
+ }
}
.dropdown-new-label {
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index e453f4a2264..c80beceae52 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -303,7 +303,7 @@
.count-badge-count,
.count-badge-button {
- border: 1px solid $gray-400;
+ border: 1px solid $border-color;
line-height: 1;
}
diff --git a/app/graphql/types/tree/submodule_type.rb b/app/graphql/types/tree/submodule_type.rb
index 8cb1e04f5ba..2b47e5c0161 100644
--- a/app/graphql/types/tree/submodule_type.rb
+++ b/app/graphql/types/tree/submodule_type.rb
@@ -7,6 +7,9 @@ module Types
implements Types::Tree::EntryType
graphql_name 'Submodule'
+
+ field :web_url, type: GraphQL::STRING_TYPE, null: true
+ field :tree_url, type: GraphQL::STRING_TYPE, null: true
end
# rubocop: enable Graphql/AuthorizeTypes
end
diff --git a/app/graphql/types/tree/tree_type.rb b/app/graphql/types/tree/tree_type.rb
index fbdc1597461..99f2a6c0235 100644
--- a/app/graphql/types/tree/tree_type.rb
+++ b/app/graphql/types/tree/tree_type.rb
@@ -15,7 +15,9 @@ module Types
Gitlab::Graphql::Representation::TreeEntry.decorate(obj.trees, obj.repository)
end
- field :submodules, Types::Tree::SubmoduleType.connection_type, null: false, calls_gitaly: true
+ field :submodules, Types::Tree::SubmoduleType.connection_type, null: false, calls_gitaly: true, resolve: -> (obj, args, ctx) do
+ Gitlab::Graphql::Representation::SubmoduleTreeEntry.decorate(obj.submodules, obj)
+ end
field :blobs, Types::Tree::BlobType.connection_type, null: false, calls_gitaly: true, resolve: -> (obj, args, ctx) do
Gitlab::Graphql::Representation::TreeEntry.decorate(obj.blobs, obj.repository)
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 67685ba4e1d..e2e007eee50 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -282,6 +282,10 @@ module IssuablesHelper
data[:hasClosingMergeRequest] = issuable.merge_requests_count(current_user) != 0 if issuable.is_a?(Issue)
+ zoom_links = Gitlab::ZoomLinkExtractor.new(issuable.description).links
+
+ data[:zoomMeetingUrl] = zoom_links.last if zoom_links.any?
+
if parent.is_a?(Group)
data[:groupPath] = parent.path
else
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 164c69ca50b..35e04b0ced3 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -9,6 +9,10 @@ module SubmoduleHelper
def submodule_links(submodule_item, ref = nil, repository = @repository)
url = repository.submodule_url_for(ref, submodule_item.path)
+ submodule_links_for_url(submodule_item.id, url, repository)
+ end
+
+ def submodule_links_for_url(submodule_item_id, url, repository)
if url == '.' || url == './'
url = File.join(Gitlab.config.gitlab.url, repository.project.full_path)
end
@@ -31,13 +35,13 @@ module SubmoduleHelper
if self_url?(url, namespace, project)
[namespace_project_path(namespace, project),
- namespace_project_tree_path(namespace, project, submodule_item.id)]
+ namespace_project_tree_path(namespace, project, submodule_item_id)]
elsif relative_self_url?(url)
- relative_self_links(url, submodule_item.id, repository.project)
+ relative_self_links(url, submodule_item_id, repository.project)
elsif github_dot_com_url?(url)
- standard_links('github.com', namespace, project, submodule_item.id)
+ standard_links('github.com', namespace, project, submodule_item_id)
elsif gitlab_dot_com_url?(url)
- standard_links('gitlab.com', namespace, project, submodule_item.id)
+ standard_links('gitlab.com', namespace, project, submodule_item_id)
else
[sanitize_submodule_url(url), nil]
end
diff --git a/app/models/active_session.rb b/app/models/active_session.rb
index f355b02c428..345767179eb 100644
--- a/app/models/active_session.rb
+++ b/app/models/active_session.rb
@@ -3,6 +3,8 @@
class ActiveSession
include ActiveModel::Model
+ SESSION_BATCH_SIZE = 200
+
attr_accessor :created_at, :updated_at,
:session_id, :ip_address,
:browser, :os, :device_name, :device_type,
@@ -106,10 +108,12 @@ class ActiveSession
Gitlab::Redis::SharedState.with do |redis|
session_keys = session_ids.map { |session_id| "#{Gitlab::Redis::SharedState::SESSION_NAMESPACE}:#{session_id}" }
- redis.mget(session_keys).compact.map do |raw_session|
- # rubocop:disable Security/MarshalLoad
- Marshal.load(raw_session)
- # rubocop:enable Security/MarshalLoad
+ session_keys.each_slice(SESSION_BATCH_SIZE).flat_map do |session_keys_batch|
+ redis.mget(session_keys_batch).compact.map do |raw_session|
+ # rubocop:disable Security/MarshalLoad
+ Marshal.load(raw_session)
+ # rubocop:enable Security/MarshalLoad
+ end
end
end
end
diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb
index 7ff06655de0..78e82955342 100644
--- a/app/models/project_feature.rb
+++ b/app/models/project_feature.rb
@@ -86,6 +86,8 @@ class ProjectFeature < ApplicationRecord
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
+ default_value_for(:pages_access_level, allows_nil: false) { |feature| feature.project&.public? ? ENABLED : PRIVATE }
+
def feature_available?(feature, user)
# This feature might not be behind a feature flag at all, so default to true
return false unless ::Feature.enabled?(feature, user, default_enabled: true)
diff --git a/app/serializers/diff_file_base_entity.rb b/app/serializers/diff_file_base_entity.rb
index d8630165e49..ee68b4b98e0 100644
--- a/app/serializers/diff_file_base_entity.rb
+++ b/app/serializers/diff_file_base_entity.rb
@@ -3,7 +3,6 @@
class DiffFileBaseEntity < Grape::Entity
include RequestAwareEntity
include BlobHelper
- include SubmoduleHelper
include DiffHelper
include TreeHelper
include ChecksCollaboration
@@ -12,12 +11,12 @@ class DiffFileBaseEntity < Grape::Entity
expose :content_sha
expose :submodule?, as: :submodule
- expose :submodule_link do |diff_file|
- memoized_submodule_links(diff_file).first
+ expose :submodule_link do |diff_file, options|
+ memoized_submodule_links(diff_file, options).first
end
expose :submodule_tree_url do |diff_file|
- memoized_submodule_links(diff_file).last
+ memoized_submodule_links(diff_file, options).last
end
expose :edit_path, if: -> (_, options) { options[:merge_request] } do |diff_file|
@@ -92,10 +91,10 @@ class DiffFileBaseEntity < Grape::Entity
private
- def memoized_submodule_links(diff_file)
+ def memoized_submodule_links(diff_file, options)
strong_memoize(:submodule_links) do
if diff_file.submodule?
- submodule_links(diff_file.blob, diff_file.content_sha, diff_file.repository)
+ options[:submodule_links].for(diff_file.blob, diff_file.content_sha)
else
[]
end
diff --git a/app/serializers/diffs_entity.rb b/app/serializers/diffs_entity.rb
index b51e4a7e6d0..1763fe5b6ab 100644
--- a/app/serializers/diffs_entity.rb
+++ b/app/serializers/diffs_entity.rb
@@ -64,7 +64,10 @@ class DiffsEntity < Grape::Entity
merge_request_path(merge_request, format: :diff)
end
- expose :diff_files, using: DiffFileEntity
+ expose :diff_files do |diffs, options|
+ submodule_links = Gitlab::SubmoduleLinks.new(merge_request.project.repository)
+ DiffFileEntity.represent(diffs.diff_files, options.merge(submodule_links: submodule_links))
+ end
expose :merge_request_diffs, using: MergeRequestDiffEntity, if: -> (_, options) { options[:merge_request_diffs]&.any? } do |diffs|
options[:merge_request_diffs]
diff --git a/app/serializers/discussion_serializer.rb b/app/serializers/discussion_serializer.rb
index 5be40e74175..8bb7e93c033 100644
--- a/app/serializers/discussion_serializer.rb
+++ b/app/serializers/discussion_serializer.rb
@@ -2,4 +2,18 @@
class DiscussionSerializer < BaseSerializer
entity DiscussionEntity
+
+ def represent(resource, opts = {}, entity_class = nil)
+ super(resource, with_additional_opts(opts), entity_class)
+ end
+
+ private
+
+ def with_additional_opts(opts)
+ additional_opts = {
+ submodule_links: Gitlab::SubmoduleLinks.new(@request.project.repository)
+ }
+
+ opts.merge(additional_opts)
+ end
end
diff --git a/app/serializers/submodule_entity.rb b/app/serializers/submodule_entity.rb
deleted file mode 100644
index e475a4f301f..00000000000
--- a/app/serializers/submodule_entity.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-# frozen_string_literal: true
-
-class SubmoduleEntity < Grape::Entity
- include RequestAwareEntity
-
- expose :id, :path, :name, :mode
-
- expose :icon do |blob|
- 'archive'
- end
-
- expose :url do |blob|
- submodule_links(blob, request).first
- end
-
- expose :tree_url do |blob|
- submodule_links(blob, request).last
- end
-
- private
-
- def submodule_links(blob, request)
- @submodule_links ||= SubmoduleHelper.submodule_links(blob, request.ref, request.repository)
- end
-end
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 034273558bb..074edf645ba 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -7,26 +7,26 @@
= render "devise/shared/error_messages", resource: resource
.name.form-group
= f.label :name, _('Full name'), class: 'label-bold'
- = f.text_field :name, class: "form-control top qa-new-user-name js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length } }, required: true, title: _("This field is required.")
+ = f.text_field :name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length }, :qa_selector => 'new_user_name_field' }, required: true, title: _("This field is required.")
.username.form-group
= f.label :username, class: 'label-bold'
- = f.text_field :username, class: "form-control middle qa-new-user-username js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length } }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
+ = f.text_field :username, class: "form-control middle js-block-emoji js-validate-length js-validate-username", :data => { :max_length => max_username_length, :max_length_message => s_("SignUp|Username is too long (maximum is %{max_length} characters).") % { max_length: max_username_length }, :qa_selector => 'new_user_username_field' }, pattern: Gitlab::PathRegex::NAMESPACE_FORMAT_REGEX_JS, required: true, title: _("Please create a username with only alphanumeric characters.")
%p.validation-error.gl-field-error-ignore.field-validation.hide= _('Username is already taken.')
%p.validation-success.gl-field-error-ignore.field-validation.hide= _('Username is available.')
%p.validation-pending.gl-field-error-ignore.field-validation.hide= _('Checking username availability...')
.form-group
= f.label :email, class: 'label-bold'
- = f.email_field :email, class: "form-control middle qa-new-user-email", required: true, title: _("Please provide a valid email address.")
+ = f.email_field :email, class: "form-control middle", data: { qa_selector: 'new_user_email_field' }, required: true, title: _("Please provide a valid email address.")
.form-group
= f.label :email_confirmation, class: 'label-bold'
- = f.email_field :email_confirmation, class: "form-control middle qa-new-user-email-confirmation", required: true, title: _("Please retype the email address.")
+ = f.email_field :email_confirmation, class: "form-control middle", data: { qa_selector: 'new_user_email_confirmation_field' }, required: true, title: _("Please retype the email address.")
.form-group.append-bottom-20#password-strength
= f.label :password, class: 'label-bold'
- = f.password_field :password, class: "form-control bottom qa-new-user-password", required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length }
+ = f.password_field :password, class: "form-control bottom", data: { qa_selector: 'new_user_password_field' }, required: true, pattern: ".{#{@minimum_password_length},}", title: _("Minimum length is %{minimum_password_length} characters.") % { minimum_password_length: @minimum_password_length }
%p.gl-field-hint.text-secondary= _('Minimum length is %{minimum_password_length} characters') % { minimum_password_length: @minimum_password_length }
- if Gitlab::CurrentSettings.current_application_settings.enforce_terms?
.form-group
- = check_box_tag :terms_opt_in, '1', false, required: true, class: 'qa-new-user-accept-terms'
+ = check_box_tag :terms_opt_in, '1', false, required: true, data: { qa_selector: 'new_user_accept_terms_checkbox' }
= label_tag :terms_opt_in do
- terms_link = link_to s_("I accept the|Terms of Service and Privacy Policy"), terms_path, target: "_blank"
- accept_terms_label = _("I accept the %{terms_link}") % { terms_link: terms_link }
@@ -36,4 +36,4 @@
- if show_recaptcha_sign_up?
= recaptcha_tags
.submit-container
- = f.submit _("Register"), class: "btn-register btn qa-new-user-register-button"
+ = f.submit _("Register"), class: "btn-register btn", data: { qa_selector: 'new_user_register_button' }
diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml
index dae9a7acf6b..5d57337a568 100644
--- a/app/views/doorkeeper/authorizations/new.html.haml
+++ b/app/views/doorkeeper/authorizations/new.html.haml
@@ -46,4 +46,4 @@
= hidden_field_tag :response_type, @pre_auth.response_type
= hidden_field_tag :scope, @pre_auth.scope
= hidden_field_tag :nonce, @pre_auth.nonce
- = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10"
+ = submit_tag _("Authorize"), class: "btn btn-success prepend-left-10", data: { qa_selector: 'authorization_button' }
diff --git a/app/views/projects/pages_domains/_form.html.haml b/app/views/projects/pages_domains/_form.html.haml
index 0e5c65a2f72..4aa1e574d93 100644
--- a/app/views/projects/pages_domains/_form.html.haml
+++ b/app/views/projects/pages_domains/_form.html.haml
@@ -33,7 +33,7 @@
= sprite_icon("status_success_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-checked")
= sprite_icon("status_failed_borderless", size: 16, css_class: "toggle-icon-svg toggle-status-unchecked")
%p.text-secondary.mt-3
- - docs_link_url = help_page_path("user/project/pages/lets_encrypt_for_gitlab_pages.md", anchor: "lets-encrypt-for-gitlab-pages")
+ - docs_link_url = help_page_path("user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md")
- docs_link_start = "<a href=\"%{docs_link_url}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"text-nowrap\">".html_safe % { docs_link_url: docs_link_url }
- docs_link_end = "</a>".html_safe
= _("Let's Encrypt is a free, automated, and open certificate authority (CA) that gives digital certificates in order to enable HTTPS (SSL/TLS) for websites. Learn more about Let's Encrypt configuration by following the %{docs_link_start}documentation on GitLab Pages%{docs_link_end}.").html_safe % { docs_link_url: docs_link_url, docs_link_start: docs_link_start, docs_link_end: docs_link_end }
diff --git a/changelogs/unreleased/48771-label-picker-line-break-on-long-label-titles.yml b/changelogs/unreleased/48771-label-picker-line-break-on-long-label-titles.yml
new file mode 100644
index 00000000000..e598247b5d8
--- /dev/null
+++ b/changelogs/unreleased/48771-label-picker-line-break-on-long-label-titles.yml
@@ -0,0 +1,5 @@
+---
+title: 'Resolve Label picker: Line break on long label titles'
+merge_request: 30610
+author:
+type: fixed
diff --git a/changelogs/unreleased/64315-mget_sessions_in_chunks.yml b/changelogs/unreleased/64315-mget_sessions_in_chunks.yml
new file mode 100644
index 00000000000..d50d86726e2
--- /dev/null
+++ b/changelogs/unreleased/64315-mget_sessions_in_chunks.yml
@@ -0,0 +1,5 @@
+---
+title: Do Redis lookup in batches in ActiveSession.sessions_from_ids
+merge_request: 30561
+author:
+type: performance
diff --git a/changelogs/unreleased/button-bug-fixes.yml b/changelogs/unreleased/button-bug-fixes.yml
deleted file mode 100644
index b63bfdf24ad..00000000000
--- a/changelogs/unreleased/button-bug-fixes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix Project Badge Button Styles
-merge_request: 30678
-author:
-type: fixed
diff --git a/changelogs/unreleased/issue-zoom-url.yml b/changelogs/unreleased/issue-zoom-url.yml
new file mode 100644
index 00000000000..e0bd5478192
--- /dev/null
+++ b/changelogs/unreleased/issue-zoom-url.yml
@@ -0,0 +1,5 @@
+---
+title: Extract zoom link from issue and pass to frontend
+merge_request: 29910
+author: raju249
+type: added
diff --git a/changelogs/unreleased/sh-bump-fog-aws.yml b/changelogs/unreleased/sh-bump-fog-aws.yml
new file mode 100644
index 00000000000..a936b81ff02
--- /dev/null
+++ b/changelogs/unreleased/sh-bump-fog-aws.yml
@@ -0,0 +1,5 @@
+---
+title: Bump fog-aws to v3.5.2
+merge_request: 30803
+author:
+type: fixed
diff --git a/db/post_migrate/20190703185326_fix_wrong_pages_access_level.rb b/db/post_migrate/20190703185326_fix_wrong_pages_access_level.rb
new file mode 100644
index 00000000000..e5981956cf5
--- /dev/null
+++ b/db/post_migrate/20190703185326_fix_wrong_pages_access_level.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+class FixWrongPagesAccessLevel < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ MIGRATION = 'FixPagesAccessLevel'
+ BATCH_SIZE = 20_000
+ BATCH_TIME = 2.minutes
+
+ disable_ddl_transaction!
+
+ class ProjectFeature < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'project_features'
+ self.inheritance_column = :_type_disabled
+ end
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ ProjectFeature,
+ MIGRATION,
+ BATCH_TIME,
+ batch_size: BATCH_SIZE)
+ end
+end
diff --git a/db/post_migrate/20190715114644_drop_project_features_pages_access_level_default.rb b/db/post_migrate/20190715114644_drop_project_features_pages_access_level_default.rb
new file mode 100644
index 00000000000..2fb0aa0f460
--- /dev/null
+++ b/db/post_migrate/20190715114644_drop_project_features_pages_access_level_default.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class DropProjectFeaturesPagesAccessLevelDefault < ActiveRecord::Migration[5.1]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ ENABLED_VALUE = 20
+
+ def change
+ change_column_default :project_features, :pages_access_level, from: ENABLED_VALUE, to: nil
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 644ca1fe970..f752211f2ec 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_07_03_130053) do
+ActiveRecord::Schema.define(version: 2019_07_15_114644) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
@@ -2507,7 +2507,7 @@ ActiveRecord::Schema.define(version: 2019_07_03_130053) do
t.datetime "created_at"
t.datetime "updated_at"
t.integer "repository_access_level", default: 20, null: false
- t.integer "pages_access_level", default: 20, null: false
+ t.integer "pages_access_level", null: false
t.index ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree
end
diff --git a/doc/administration/pages/img/lets_encrypt_integration_v12_1.png b/doc/administration/pages/img/lets_encrypt_integration_v12_1.png
new file mode 100644
index 00000000000..5ab63074e12
--- /dev/null
+++ b/doc/administration/pages/img/lets_encrypt_integration_v12_1.png
Binary files differ
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 3cabe8eb16e..774e7056845 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -265,6 +265,23 @@ verification requirement. Navigate to `Admin area âž” Settings` and uncheck
**Require users to prove ownership of custom domains** in the Pages section.
This setting is enabled by default.
+### Let's Encrypt integration
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28996) in GitLab 12.1.
+
+[GitLab Pages' Let's Encrypt integration](../../user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md)
+allows users to add Let's Encrypt SSL certificates for GitLab Pages
+sites served under a custom domain.
+
+To enable it, you'll need to:
+
+1. Choose an email on which you will recieve notifications about expiring domains.
+1. Navigate to your instance's **Admin Area > Settings > Preferences** and expand **Pages** settings.
+1. Enter the email for receiving notifications and accept Let's Encrypt's Terms of Service as shown below.
+1. Click **Save changes**.
+
+![Let's Encrypt settings](img/lets_encrypt_integration_v12_1.png)
+
### Access control
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/33422) in GitLab 11.5.
diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md
index 83a9035c001..f3896c5232c 100644
--- a/doc/ci/docker/using_docker_images.md
+++ b/doc/ci/docker/using_docker_images.md
@@ -35,8 +35,8 @@ sudo gitlab-runner register \
--description "docker-ruby-2.1" \
--executor "docker" \
--docker-image ruby:2.1 \
- --docker-postgres latest \
- --docker-mysql latest
+ --docker-services postgres:latest \
+ --docker-services mysql:latest
```
The registered runner will use the `ruby:2.1` Docker image and will run two
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index 5a302392c54..9295dcfd4e0 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -37,6 +37,7 @@ The following table lists examples with step-by-step tutorials that are containe
| Python on Heroku | [Test and deploy a Python application with GitLab CI/CD](test-and-deploy-python-application-to-heroku.md). |
| Ruby on Heroku | [Test and deploy a Ruby application with GitLab CI/CD](test-and-deploy-ruby-application-to-heroku.md). |
| Scala on Heroku | [Test and deploy a Scala application to Heroku](test-scala-application.md). |
+| Parallel testing Ruby & JS | [GitLab CI parallel jobs testing for Ruby & JavaScript projects](https://docs.knapsackpro.com/2019/how-to-run-parallel-jobs-for-rspec-tests-on-gitlab-ci-pipeline-and-speed-up-ruby-javascript-testing). |
### Contributing examples
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index b1b193701c3..001f951ebb8 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1779,6 +1779,10 @@ test:
parallel: 5
```
+TIP: **Tip:**
+Parallelize tests suites across parallel jobs.
+Different languages have different tools to facilitate this.
+
### `trigger` **(PREMIUM)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/8997) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.8.
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 0dd0fd3f136..09bd306363c 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -149,6 +149,8 @@ using environment variables.
| `DS_DOCKER_CLIENT_NEGOTIATION_TIMEOUT` | Time limit for Docker client negotiation. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `DS_PULL_ANALYZER_IMAGE_TIMEOUT` | Time limit when pulling the image of an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
| `DS_RUN_ANALYZER_TIMEOUT` | Time limit when running an analyzer. Timeouts are parsed using Go's [`ParseDuration`](https://golang.org/pkg/time/#ParseDuration). Valid time units are `ns`, `us` (or `µs`), `ms`, `s`, `m`, `h`. For example, `300ms`, `1.5h`, or `2h45m`. |
+| `PIP_INDEX_URL` | Base URL of Python Package Index (default https://pypi.org/simple). |
+| `PIP_EXTRA_INDEX_URL` | Array of [extra URLs](https://pip.pypa.io/en/stable/reference/pip_install/#cmdoption-extra-index-url) of package indexes to use in addition to `PIP_INDEX_URL`. Comma separated. |
## Reports JSON format
diff --git a/doc/user/project/autocomplete_characters.md b/doc/user/project/autocomplete_characters.md
new file mode 100644
index 00000000000..9ebf7f821a1
--- /dev/null
+++ b/doc/user/project/autocomplete_characters.md
@@ -0,0 +1,48 @@
+# Autocomplete characters
+
+The autocomplete characters provide a quick way of entering field values into
+Markdown fields. When you start typing a word in a Markdown field with one of
+the following characters, GitLab progressively autocompletes against a set of
+matching values. The string matching is not case sensitive.
+
+| Character | Autocompletes |
+| :-------- | :------------ |
+| `~` | Labels |
+| `%` | Milestones |
+| `@` | Users and groups |
+| `#` | Issues |
+| `!` | Merge requests |
+| `&` | Epics |
+| `$` | Snippets |
+| `:` | Emoji |
+| `/` | Quick Actions |
+
+Up to 5 of the most relevant matches are displayed in a popup list. When you
+select an item from the list, the value is entered in the field. The more
+characters you enter, the more precise the matches are.
+
+Autocomplete characters are useful when combined with [Quick Actions](quick_actions.md).
+
+## Example
+
+Assume your GitLab instance includes the following users:
+
+| Username | Name |
+| :-------------- | :--- |
+| alessandra | Rosy Grant |
+| lawrence.white | Kelsey Kerluke |
+| leanna | Rosemarie Rogahn |
+| logan_gutkowski | Lee Wuckert |
+| shelba | Josefine Haley |
+
+In an Issue comment, entering `@l` results in the following popup list
+appearing. Note that user `shelba` is not included, because the list includes
+only the 5 users most relevant to the Issue.
+
+![Popup list which includes users whose username or name contains the letter `l`](img/autocomplete_characters_example1_v12_0.png)
+
+If you continue to type, `@le`, the popup list changes to the following. The
+popup now only includes users where `le` appears in their username, or a word in
+their name.
+
+![Popup list which includes users whose username or name contains the string `le`](img/autocomplete_characters_example2_v12_0.png)
diff --git a/doc/user/project/img/autocomplete_characters_example1_v12_0.png b/doc/user/project/img/autocomplete_characters_example1_v12_0.png
new file mode 100755
index 00000000000..9c6fa923b80
--- /dev/null
+++ b/doc/user/project/img/autocomplete_characters_example1_v12_0.png
Binary files differ
diff --git a/doc/user/project/img/autocomplete_characters_example2_v12_0.png b/doc/user/project/img/autocomplete_characters_example2_v12_0.png
new file mode 100755
index 00000000000..b2e8a782a0b
--- /dev/null
+++ b/doc/user/project/img/autocomplete_characters_example2_v12_0.png
Binary files differ
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 0ffa69b6b78..7307c5b8991 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -52,6 +52,9 @@ When you create a project in GitLab, you'll have access to a large number of
templates for issue and merge request description fields for your project
- [Slash commands (quick actions)](quick_actions.md): Textual shortcuts for
common actions on issues or merge requests
+- [Autocomplete characters](autocomplete_characters.md): Autocomplete
+ references to users, groups, issues, merge requests, and other GitLab
+ elements.
- [Web IDE](web_ide/index.md)
**GitLab CI/CD:**
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png
new file mode 100644
index 00000000000..2e825e84d92
--- /dev/null
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/img/lets_encrypt_integration_v12_1.png
Binary files differ
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
index 6c0d3e9e9d3..54ecc42d2b9 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/index.md
@@ -179,20 +179,39 @@ From that page, you can view, add, and remove them.
### Redirecting `www.domain.com` to `domain.com` with Cloudflare
-If you use Cloudflare, you can redirect `www` to `domain.com` without adding both
-`www.domain.com` and `domain.com` to GitLab. This happens due to a [Cloudflare feature that creates
-a 301 redirect as a "page rule"](https://gitlab.com/gitlab-org/gitlab-ce/issues/48848#note_87314849) for redirecting `www.domain.com` to `domain.com`. In this case,
-you can use the following setup:
+If you use Cloudflare, you can redirect `www` to `domain.com`
+without adding both `www.domain.com` and `domain.com` to GitLab.
+
+To do so, you can use Cloudflare's page rules associated to a
+CNAME record to redirect `www.domain.com` to `domain.com`. You
+can use the following setup:
1. In Cloudflare, create a DNS `A` record pointing `domain.com` to `35.185.44.232`.
-1. In GitLab, add the domain to GitLab Pages.
+1. In GitLab, add the domain to GitLab Pages and get the verification code.
1. In Cloudflare, create a DNS `TXT` record to verify your domain.
+1. In GitLab, verify your domain.
1. In Cloudflare, create a DNS `CNAME` record pointing `www` to `domain.com`.
+1. In Cloudflare, add a Page Rule pointing `www.domain,com` to `domain.com`:
+ - Navigate to your domain's dashboard and click **Page Rules**
+ on the top nav.
+ - Click **Create Page Rule**.
+ - Enter the domain `www.domain.com` and click **+ Add a Setting**.
+ - From the dropdown menu, choose **Forwarding URL**, then select the
+ status code **301 - Permanent Redirect**.
+ - Enter the destination URL `https://domain.com`.
## Adding an SSL/TLS certificate to Pages
Read this document for an [overview on SSL/TLS certification](ssl_tls_concepts.md).
+To secure your custom domain with GitLab Pages you can opt by:
+
+- Using the [Let's Encrypt integration with GitLab Pages](lets_encrypt_integration.md),
+ which automatically obtains and renews SSL certificates
+ for your Pages domains.
+- Manually adding SSL/TLS certificates to GitLab Pages websites
+ by following the steps below.
+
### Requirements
- A GitLab Pages website up and running accessible via a custom domain.
@@ -244,6 +263,7 @@ To enable this setting:
1. Navigate to your project's **Settings > Pages**.
1. Tick the checkbox **Force HTTPS (requires valid certificates)**.
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
@@ -254,4 +274,4 @@ questions that you know someone might ask.
Each scenario can be a third-level heading, e.g. `### Getting error message X`.
If you have none to add when creating a doc, leave this section in place
-but commented out to help encourage others to add to it in the future. --> \ No newline at end of file
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
new file mode 100644
index 00000000000..7675a5dd9d4
--- /dev/null
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
@@ -0,0 +1,68 @@
+---
+type: reference
+description: "Automatic Let's Encrypt SSL certificates for GitLab Pages."
+---
+
+# GitLab Pages integration with Let's Encrypt
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/28996) in GitLab 12.1.
+
+The GitLab Pages integration with Let's Encrypt (LE) allows you
+to use LE certificates for your Pages website with custom domains
+without the hassle of having to issue and update them yourself;
+GitLab does it for you, out-of-the-box.
+
+[Let's Encrypt](https://letsencrypt.org) is a free, automated, and
+open source Certificate Authority.
+
+## Requirements
+
+Before you can enable automatic provisioning of a SSL certificate for your domain, make sure you have:
+
+- Created a [project](../getting_started_part_two.md) in GitLab
+ containing your website's source code.
+- Acquired a domain (`example.com`) and added a [DNS entry](index.md)
+ pointing it to your Pages website.
+- [Added your domain to your Pages project](index.md#1-add-a-custom-domain-to-pages)
+ and verified your ownership.
+- Have your website up and running, accessible through your custom domain.
+
+NOTE: **Note:**
+GitLab's Let's Encrypt integration is enabled and available on GitLab.com.
+For **self-managed** GitLab instances, make sure your administrator has
+[enabled it](../../../../administration/pages/index.md#lets-encrypt-integration).
+
+## Enabling Let's Encrypt integration for your custom domain
+
+Once you've met the requirements, to enable Let's Encrypt integration:
+
+1. Navigate to your project's **Settings > Pages**.
+1. Find your domain and click **Details**.
+1. Click **Edit** in the top-right corner.
+1. Enable Let's Encrypt integration by switching **Automatic certificate management using Let's Encrypt**:
+
+ ![Enable Let's Encrypt](img/lets_encrypt_integration_v12_1.png)
+
+1. Click **Save changes**.
+
+Once enabled, GitLab will obtain a LE certificate and add it to the
+associated Pages domain. It will be also renewed automatically by GitLab.
+
+> **Notes:**
+>
+> - Issuing the certificate and updating Pages configuration
+> **can take up to an hour**.
+> - If you already have SSL certificate in domain settings it
+> will continue to work until it will be replaced by Let's Encrypt's certificate.
+
+<!-- ## Troubleshooting
+
+Include any troubleshooting steps that you can foresee. If you know beforehand what issues
+one might have when setting this up, or when something is changed, or on upgrading, it's
+important to describe those, too. Think of things that may go wrong and include them here.
+This is important to minimize requests for support, and to avoid doc comments with
+questions that you know someone might ask.
+
+Each scenario can be a third-level heading, e.g. `### Getting error message X`.
+If you have none to add when creating a doc, leave this section in place
+but commented out to help encourage others to add to it in the future. -->
diff --git a/doc/user/project/pages/index.md b/doc/user/project/pages/index.md
index e9d2e9a0059..25944b029d7 100644
--- a/doc/user/project/pages/index.md
+++ b/doc/user/project/pages/index.md
@@ -143,8 +143,8 @@ To learn more about configuration options for GitLab Pages, read the following:
| [Exploring GitLab Pages](introduction.md) | Requirements, technical aspects, specific GitLab CI's configuration options, Access Control, custom 404 pages, limitations, FAQ. |
|---+---|
| [Custom domains and SSL/TLS Certificates](custom_domains_ssl_tls_certification/index.md) | How to add custom domains and subdomains to your website, configure DNS records and SSL/TLS certificates. |
+| [Let's Encrypt integration](custom_domains_ssl_tls_certification/lets_encrypt_integration.md) | Secure your Pages sites with Let's Encrypt certificates automatically obtained and renewed by GitLab. |
| [CloudFlare certificates](https://about.gitlab.com/2017/02/07/setting-up-gitlab-pages-with-cloudflare-certificates/) | Secure your Pages site with CloudFlare certificates. |
-| [Let's Encrypt certificates](lets_encrypt_for_gitlab_pages.md) | Secure your Pages site with Let's Encrypt certificates. |
|---+---|
| [Static vs dynamic websites](https://about.gitlab.com/2016/06/03/ssg-overview-gitlab-pages-part-1-dynamic-x-static/) | A conceptual overview on static versus dynamic sites. |
| [Modern static site generators](https://about.gitlab.com/2016/06/10/ssg-overview-gitlab-pages-part-2/) | A conceptual overview on SSGs. |
diff --git a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
index cc129f90b7a..1338c7e58f5 100644
--- a/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
+++ b/doc/user/project/pages/lets_encrypt_for_gitlab_pages.md
@@ -1,10 +1,15 @@
---
-description: "How to secure GitLab Pages websites with Let's Encrypt."
+description: "How to secure GitLab Pages websites with Let's Encrypt (manual process, deprecated)."
type: howto
-last_updated: 2019-06-04
+last_updated: 2019-07-15
---
-# Let's Encrypt for GitLab Pages
+# Let's Encrypt for GitLab Pages (manual process, deprecated)
+
+CAUTION: **Warning:**
+This method is still valid but was **deprecated** in favor of the
+[Let's Encrypt integration](custom_domains_ssl_tls_certification/lets_encrypt_integration.md)
+introduced in GitLab 12.1.
If you have a GitLab Pages website served under your own domain,
you might want to secure it with a SSL/TSL certificate.
diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md
index e60da6a3e59..df82daa3da3 100644
--- a/doc/user/project/pipelines/settings.md
+++ b/doc/user/project/pipelines/settings.md
@@ -89,6 +89,22 @@ in the jobs table.
A few examples of known coverage tools for a variety of languages can be found
in the pipelines settings page.
+### Removing color codes
+
+Some test coverage tools output with ANSI color codes that won't be
+parsed correctly by the regular expression and will cause coverage
+parsing to fail.
+
+If your coverage tool doesn't provide an option to disable color
+codes in the output, you can pipe the output of the coverage tool through a
+small one line script that will strip the color codes off.
+
+For example:
+
+```bash
+lein cloverage | perl -pe 's/\e\[?.*?[\@-~]//g'
+```
+
## Visibility of pipelines
Access to pipelines and job details (including output of logs and artifacts)
diff --git a/doc/workflow/time_tracking.md b/doc/workflow/time_tracking.md
index 4286a3625a2..b55c6b2e3df 100644
--- a/doc/workflow/time_tracking.md
+++ b/doc/workflow/time_tracking.md
@@ -75,7 +75,7 @@ Default conversion rates are 1mo = 4w, 1w = 5d and 1d = 8h.
### Limit displayed units to hours
-> Introduced in GitLab 12.0.
+> Introduced in GitLab 12.1.
The display of time units can be limited to hours through the option in **Admin Area > Settings > Preferences** under 'Localization'.
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 0a9515f1dd2..494da770279 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -294,7 +294,6 @@ module API
expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) {
options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project)
}
- expose :external_authorization_classification_label
expose :auto_devops_enabled?, as: :auto_devops_enabled
expose :auto_devops_deploy_strategy do |project, options|
project.auto_devops.nil? ? 'continuous' : project.auto_devops.deploy_strategy
diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb
index 0e21a7a66fd..833e3b9ebaf 100644
--- a/lib/api/helpers/projects_helpers.rb
+++ b/lib/api/helpers/projects_helpers.rb
@@ -42,7 +42,6 @@ module API
optional :printing_merge_request_link_enabled, type: Boolean, desc: 'Show link to create/view merge request when pushing from the command line'
optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests'
optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md"
- optional :external_authorization_classification_label, type: String, desc: 'The classification label for the project'
optional :ci_default_git_depth, type: Integer, desc: 'Default number of revisions for shallow cloning'
optional :auto_devops_enabled, type: Boolean, desc: 'Flag indication if Auto DevOps is enabled'
optional :auto_devops_deploy_strategy, type: String, values: %w(continuous manual timed_incremental), desc: 'Auto Deploy strategy'
@@ -94,7 +93,6 @@ module API
:visibility,
:wiki_access_level,
:avatar,
- :external_authorization_classification_label,
# TODO: remove in API v5, replaced by *_access_level
:issues_enabled,
@@ -105,6 +103,9 @@ module API
:snippets_enabled
]
end
+
+ def filter_attributes_using_license!(attrs)
+ end
end
end
end
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index a7d62014509..0923d31f5ff 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -145,6 +145,7 @@ module API
post do
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
+ filter_attributes_using_license!(attrs)
project = ::Projects::CreateService.new(current_user, attrs).execute
if project.saved?
@@ -179,6 +180,7 @@ module API
attrs = declared_params(include_missing: false)
attrs = translate_params_for_compatibility(attrs)
+ filter_attributes_using_license!(attrs)
project = ::Projects::CreateService.new(user, attrs).execute
if project.saved?
@@ -292,7 +294,7 @@ module API
authorize! :change_visibility_level, user_project if attrs[:visibility].present?
attrs = translate_params_for_compatibility(attrs)
-
+ filter_attributes_using_license!(attrs)
verify_update_project_attrs!(user_project, attrs)
result = ::Projects::UpdateService.new(user_project, current_user, attrs).execute
diff --git a/lib/gitlab/background_migration/fix_pages_access_level.rb b/lib/gitlab/background_migration/fix_pages_access_level.rb
new file mode 100644
index 00000000000..0d49f3dd8c5
--- /dev/null
+++ b/lib/gitlab/background_migration/fix_pages_access_level.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # corrects stored pages access level on db depending on project visibility
+ class FixPagesAccessLevel
+ # Copy routable here to avoid relying on application logic
+ module Routable
+ def build_full_path
+ if parent && path
+ parent.build_full_path + '/' + path
+ else
+ path
+ end
+ end
+ end
+
+ # Namespace
+ class Namespace < ApplicationRecord
+ self.table_name = 'namespaces'
+ self.inheritance_column = :_type_disabled
+
+ include Routable
+
+ belongs_to :parent, class_name: "Namespace"
+ end
+
+ # Project
+ class Project < ActiveRecord::Base
+ self.table_name = 'projects'
+ self.inheritance_column = :_type_disabled
+
+ include Routable
+
+ belongs_to :namespace
+ alias_method :parent, :namespace
+ alias_attribute :parent_id, :namespace_id
+
+ PRIVATE = 0
+ INTERNAL = 10
+ PUBLIC = 20
+
+ def pages_deployed?
+ Dir.exist?(public_pages_path)
+ end
+
+ def public_pages_path
+ File.join(pages_path, 'public')
+ end
+
+ def pages_path
+ # TODO: when we migrate Pages to work with new storage types, change here to use disk_path
+ File.join(Settings.pages.path, build_full_path)
+ end
+ end
+
+ # ProjectFeature
+ class ProjectFeature < ActiveRecord::Base
+ include ::EachBatch
+
+ self.table_name = 'project_features'
+
+ belongs_to :project
+
+ PRIVATE = 10
+ ENABLED = 20
+ PUBLIC = 30
+ end
+
+ def perform(start_id, stop_id)
+ fix_public_access_level(start_id, stop_id)
+
+ make_internal_projects_public(start_id, stop_id)
+
+ fix_private_access_level(start_id, stop_id)
+ end
+
+ private
+
+ def access_control_is_enabled
+ @access_control_is_enabled = Gitlab.config.pages.access_control
+ end
+
+ # Public projects are allowed to have only enabled pages_access_level
+ # which is equivalent to public
+ def fix_public_access_level(start_id, stop_id)
+ project_features(start_id, stop_id, ProjectFeature::PUBLIC, Project::PUBLIC).each_batch do |features|
+ features.update_all(pages_access_level: ProjectFeature::ENABLED)
+ end
+ end
+
+ # If access control is disabled and project has pages deployed
+ # project will become unavailable when access control will become enabled
+ # we make these projects public to avoid negative surprise to user
+ def make_internal_projects_public(start_id, stop_id)
+ return if access_control_is_enabled
+
+ project_features(start_id, stop_id, ProjectFeature::ENABLED, Project::INTERNAL).find_each do |project_feature|
+ next unless project_feature.project.pages_deployed?
+
+ project_feature.update(pages_access_level: ProjectFeature::PUBLIC)
+ end
+ end
+
+ # Private projects are not allowed to have enabled access level, only `private` and `public`
+ # If access control is enabled, these projects currently behave as if the have `private` pages_access_level
+ # if access control is disabled, these projects currently behave as if the have `public` pages_access_level
+ # so we preserve this behaviour for projects with pages already deployed
+ # for project without pages we always set `private` access_level
+ def fix_private_access_level(start_id, stop_id)
+ project_features(start_id, stop_id, ProjectFeature::ENABLED, Project::PRIVATE).find_each do |project_feature|
+ if access_control_is_enabled
+ project_feature.update!(pages_access_level: ProjectFeature::PRIVATE)
+ else
+ fixed_access_level = project_feature.project.pages_deployed? ? ProjectFeature::PUBLIC : ProjectFeature::PRIVATE
+ project_feature.update!(pages_access_level: fixed_access_level)
+ end
+ end
+ end
+
+ def project_features(start_id, stop_id, pages_access_level, project_visibility_level)
+ ProjectFeature.where(id: start_id..stop_id).joins(:project)
+ .where(pages_access_level: pages_access_level)
+ .where(projects: { visibility_level: project_visibility_level })
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
index f176771775e..89eccce69f6 100644
--- a/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Dependency-Scanning.gitlab-ci.yml
@@ -41,6 +41,8 @@ dependency_scanning:
DS_PULL_ANALYZER_IMAGE_TIMEOUT \
DS_RUN_ANALYZER_TIMEOUT \
DS_PYTHON_VERSION \
+ PIP_INDEX_URL \
+ PIP_EXTRA_INDEX_URL \
) \
--volume "$PWD:/code" \
--volume /var/run/docker.sock:/var/run/docker.sock \
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index b7b7578cef9..a7d9ba51277 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -464,6 +464,18 @@ module Gitlab
end
end
+ # Returns path to url mappings for submodules
+ #
+ # Ex.
+ # @repository.submodule_urls_for('master')
+ # # => { 'rack' => 'git@localhost:rack.git' }
+ #
+ def submodule_urls_for(ref)
+ wrapped_gitaly_errors do
+ gitaly_submodule_urls_for(ref)
+ end
+ end
+
# Return total commits count accessible from passed ref
def commit_count(ref)
wrapped_gitaly_errors do
@@ -1059,12 +1071,16 @@ module Gitlab
return unless commit_object && commit_object.type == :COMMIT
+ urls = gitaly_submodule_urls_for(ref)
+ urls && urls[path]
+ end
+
+ def gitaly_submodule_urls_for(ref)
gitmodules = gitaly_commit_client.tree_entry(ref, '.gitmodules', Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE)
return unless gitmodules
- found_module = GitmodulesParser.new(gitmodules.data).parse[path]
-
- found_module && found_module['url']
+ submodules = GitmodulesParser.new(gitmodules.data).parse
+ submodules.transform_values { |submodule| submodule['url'] }
end
# Returns true if the given ref name exists
diff --git a/lib/gitlab/graphql/representation/submodule_tree_entry.rb b/lib/gitlab/graphql/representation/submodule_tree_entry.rb
new file mode 100644
index 00000000000..65716dff75d
--- /dev/null
+++ b/lib/gitlab/graphql/representation/submodule_tree_entry.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Graphql
+ module Representation
+ class SubmoduleTreeEntry < SimpleDelegator
+ class << self
+ def decorate(submodules, tree)
+ repository = tree.repository
+ submodule_links = Gitlab::SubmoduleLinks.new(repository)
+
+ submodules.map do |submodule|
+ self.new(submodule, submodule_links.for(submodule, tree.sha))
+ end
+ end
+ end
+
+ def initialize(submodule, submodule_links)
+ @submodule_links = submodule_links
+
+ super(submodule)
+ end
+
+ def web_url
+ @submodule_links.first
+ end
+
+ def tree_url
+ @submodule_links.last
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/submodule_links.rb b/lib/gitlab/submodule_links.rb
new file mode 100644
index 00000000000..a6c0369d864
--- /dev/null
+++ b/lib/gitlab/submodule_links.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class SubmoduleLinks
+ include Gitlab::Utils::StrongMemoize
+
+ def initialize(repository)
+ @repository = repository
+ end
+
+ def for(submodule, sha)
+ submodule_url = submodule_url_for(sha)[submodule.path]
+ SubmoduleHelper.submodule_links_for_url(submodule.id, submodule_url, repository)
+ end
+
+ private
+
+ attr_reader :repository
+
+ def submodule_url_for(sha)
+ strong_memoize(:"submodule_links_for_#{sha}") do
+ repository.submodule_urls_for(sha)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/zoom_link_extractor.rb b/lib/gitlab/zoom_link_extractor.rb
new file mode 100644
index 00000000000..d9994898a08
--- /dev/null
+++ b/lib/gitlab/zoom_link_extractor.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+# Detect links matching the following formats:
+# Zoom Start links: https://zoom.us/s/<meeting-id>
+# Zoom Join links: https://zoom.us/j/<meeting-id>
+# Personal Zoom links: https://zoom.us/my/<meeting-id>
+# Vanity Zoom links: https://gitlab.zoom.us/j/<meeting-id> (also /s and /my)
+
+module Gitlab
+ class ZoomLinkExtractor
+ ZOOM_REGEXP = %r{https://(?:[\w-]+\.)?zoom\.us/(?:s|j|my)/\S+}.freeze
+
+ def initialize(text)
+ @text = text.to_s
+ end
+
+ def links
+ @text.scan(ZOOM_REGEXP)
+ end
+ end
+end
diff --git a/qa/qa/page/main/oauth.rb b/qa/qa/page/main/oauth.rb
index 5f6ddb9a114..2b1a9ab2b6a 100644
--- a/qa/qa/page/main/oauth.rb
+++ b/qa/qa/page/main/oauth.rb
@@ -5,7 +5,7 @@ module QA
module Main
class OAuth < Page::Base
view 'app/views/doorkeeper/authorizations/new.html.haml' do
- element :authorization_button, 'submit_tag _("Authorize")' # rubocop:disable QA/ElementWithPattern
+ element :authorization_button
end
def needs_authorization?
@@ -13,7 +13,7 @@ module QA
end
def authorize!
- click_button 'Authorize'
+ click_element :authorization_button
end
end
end
diff --git a/qa/qa/page/main/sign_up.rb b/qa/qa/page/main/sign_up.rb
index 46a105003d0..c47d2ce9c74 100644
--- a/qa/qa/page/main/sign_up.rb
+++ b/qa/qa/page/main/sign_up.rb
@@ -5,28 +5,28 @@ module QA
module Main
class SignUp < Page::Base
view 'app/views/devise/shared/_signup_box.html.haml' do
- element :new_user_name
- element :new_user_username
- element :new_user_email
- element :new_user_email_confirmation
- element :new_user_password
+ element :new_user_name_field
+ element :new_user_username_field
+ element :new_user_email_field
+ element :new_user_email_confirmation_field
+ element :new_user_password_field
element :new_user_register_button
- element :new_user_accept_terms
+ element :new_user_accept_terms_checkbox
end
def sign_up!(user)
- fill_element :new_user_name, user.name
- fill_element :new_user_username, user.username
- fill_element :new_user_email, user.email
- fill_element :new_user_email_confirmation, user.email
- fill_element :new_user_password, user.password
+ fill_element :new_user_name_field, user.name
+ fill_element :new_user_username_field, user.username
+ fill_element :new_user_email_field, user.email
+ fill_element :new_user_email_confirmation_field, user.email
+ fill_element :new_user_password_field, user.password
- check_element :new_user_accept_terms if has_element?(:new_user_accept_terms)
+ check_element :new_user_accept_terms_checkbox if has_element?(:new_user_accept_terms_checkbox)
signed_in = retry_until do
click_element :new_user_register_button
- Page::Main::Menu.act { has_personal_area? }
+ Page::Main::Menu.perform(&:has_personal_area?)
end
raise "Failed to register and sign in" unless signed_in
diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb
index c94e1e85256..3c9e8085748 100644
--- a/qa/qa/page/project/sub_menus/common.rb
+++ b/qa/qa/page/project/sub_menus/common.rb
@@ -12,7 +12,11 @@ module QA
end
def within_submenu
- within('.fly-out-list') do
+ if has_css?('.fly-out-list')
+ within('.fly-out-list') do
+ yield
+ end
+ else
yield
end
end
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index 45cb317e0eb..7969de726e4 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -9,6 +9,7 @@ module QA
:description,
:source_branch,
:target_branch,
+ :target_new_branch,
:assignee,
:milestone,
:labels,
@@ -27,6 +28,7 @@ module QA
Repository::ProjectPush.fabricate! do |resource|
resource.project = project
resource.branch_name = 'master'
+ resource.new_branch = @target_new_branch
resource.remote_branch = target_branch
end
end
@@ -52,6 +54,7 @@ module QA
@labels = []
@file_name = "added_file.txt"
@file_content = "File Added"
+ @target_new_branch = true
end
def fabricate!
diff --git a/spec/features/issues/bulk_assignment_labels_spec.rb b/spec/features/issues/bulk_assignment_labels_spec.rb
index 06cb2e36334..7be5961af09 100644
--- a/spec/features/issues/bulk_assignment_labels_spec.rb
+++ b/spec/features/issues/bulk_assignment_labels_spec.rb
@@ -381,7 +381,7 @@ describe 'Issues > Labels bulk assignment' do
if unmark
items.map do |item|
# Make sure we are unmarking the item no matter the state it has currently
- click_link item until find('a', text: item)[:class] == 'label-item'
+ click_link item until find('a', text: item)[:class].include? 'label-item'
end
end
end
diff --git a/spec/frontend/issue_show/components/pinned_links_spec.js b/spec/frontend/issue_show/components/pinned_links_spec.js
index 50041667a61..77da3390918 100644
--- a/spec/frontend/issue_show/components/pinned_links_spec.js
+++ b/spec/frontend/issue_show/components/pinned_links_spec.js
@@ -5,10 +5,6 @@ import PinnedLinks from '~/issue_show/components/pinned_links.vue';
const localVue = createLocalVue();
const plainZoomUrl = 'https://zoom.us/j/123456789';
-const vanityZoomUrl = 'https://gitlab.zoom.us/j/123456789';
-const startZoomUrl = 'https://zoom.us/s/123456789';
-const personalZoomUrl = 'https://zoom.us/my/hunter-zoloman';
-const randomUrl = 'https://zoom.us.com';
describe('PinnedLinks', () => {
let wrapper;
@@ -27,7 +23,7 @@ describe('PinnedLinks', () => {
localVue,
sync: false,
propsData: {
- descriptionHtml: '',
+ zoomMeetingUrl: null,
...props,
},
});
@@ -35,55 +31,15 @@ describe('PinnedLinks', () => {
it('displays Zoom link', () => {
createComponent({
- descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`,
+ zoomMeetingUrl: `<a href="${plainZoomUrl}">Zoom</a>`,
});
expect(link.text).toBe('Join Zoom meeting');
});
- it('detects plain Zoom link', () => {
+ it('does not render if there are no links', () => {
createComponent({
- descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(plainZoomUrl);
- });
-
- it('detects vanity Zoom link', () => {
- createComponent({
- descriptionHtml: `<a href="${vanityZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(vanityZoomUrl);
- });
-
- it('detects Zoom start meeting link', () => {
- createComponent({
- descriptionHtml: `<a href="${startZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(startZoomUrl);
- });
-
- it('detects personal Zoom room link', () => {
- createComponent({
- descriptionHtml: `<a href="${personalZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(personalZoomUrl);
- });
-
- it('only renders final Zoom link in description', () => {
- createComponent({
- descriptionHtml: `<a href="${plainZoomUrl}">Zoom</a><a href="${vanityZoomUrl}">Zoom</a>`,
- });
-
- expect(link.href).toBe(vanityZoomUrl);
- });
-
- it('does not render for other links', () => {
- createComponent({
- descriptionHtml: `<a href="${randomUrl}">Some other link</a>`,
+ zoomMeetingUrl: null,
});
expect(wrapper.find(GlLink).exists()).toBe(false);
diff --git a/spec/frontend/repository/components/table/row_spec.js b/spec/frontend/repository/components/table/row_spec.js
index c566057ad3f..e539c560975 100644
--- a/spec/frontend/repository/components/table/row_spec.js
+++ b/spec/frontend/repository/components/table/row_spec.js
@@ -1,5 +1,5 @@
import { shallowMount, RouterLinkStub } from '@vue/test-utils';
-import { GlBadge } from '@gitlab/ui';
+import { GlBadge, GlLink } from '@gitlab/ui';
import { visitUrl } from '~/lib/utils/url_utility';
import TableRow from '~/repository/components/table/row.vue';
@@ -142,4 +142,18 @@ describe('Repository table row component', () => {
expect(vm.find(GlBadge).exists()).toBe(true);
});
+
+ it('renders commit and web links with href for submodule', () => {
+ factory({
+ id: '1',
+ path: 'test',
+ type: 'commit',
+ url: 'https://test.com',
+ submoduleTreeUrl: 'https://test.com/commit',
+ currentPath: '/',
+ });
+
+ expect(vm.find('a').attributes('href')).toEqual('https://test.com');
+ expect(vm.find(GlLink).attributes('href')).toEqual('https://test.com/commit');
+ });
});
diff --git a/spec/graphql/types/tree/submodule_type_spec.rb b/spec/graphql/types/tree/submodule_type_spec.rb
index bdb3149b41c..768eccba68c 100644
--- a/spec/graphql/types/tree/submodule_type_spec.rb
+++ b/spec/graphql/types/tree/submodule_type_spec.rb
@@ -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) }
+ it { expect(described_class).to have_graphql_fields(:id, :name, :type, :path, :flat_path, :web_url, :tree_url) }
end
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index 1d1446eaa30..3c8179460ac 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -202,5 +202,46 @@ describe IssuablesHelper do
}
expect(helper.issuable_initial_data(issue)).to match(hash_including(expected_data))
end
+
+ describe '#zoomMeetingUrl in issue' do
+ let(:issue) { create(:issue, author: user, description: description) }
+
+ before do
+ assign(:project, issue.project)
+ end
+
+ context 'no zoom links in the issue description' do
+ let(:description) { 'issue text' }
+
+ it 'does not set zoomMeetingUrl' do
+ expect(helper.issuable_initial_data(issue))
+ .not_to include(:zoomMeetingUrl)
+ end
+ end
+
+ context 'no zoom links in the issue description if it has link but not a zoom link' do
+ let(:description) { 'issue text https://stackoverflow.com/questions/22' }
+
+ it 'does not set zoomMeetingUrl' do
+ expect(helper.issuable_initial_data(issue))
+ .not_to include(:zoomMeetingUrl)
+ end
+ end
+
+ context 'with two zoom links in description' do
+ let(:description) do
+ <<~TEXT
+ issue text and
+ zoom call on https://zoom.us/j/123456789 this url
+ and new zoom url https://zoom.us/s/lastone and some more text
+ TEXT
+ end
+
+ it 'sets zoomMeetingUrl value to the last url' do
+ expect(helper.issuable_initial_data(issue))
+ .to include(zoomMeetingUrl: 'https://zoom.us/s/lastone')
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index a28b95e5bff..41b898df112 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -256,6 +256,22 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#submodule_urls_for' do
+ let(:ref) { 'master' }
+
+ it 'returns url mappings for submodules' do
+ urls = repository.submodule_urls_for(ref)
+
+ expect(urls).to eq({
+ "deeper/nested/six" => "git://github.com/randx/six.git",
+ "gitlab-grack" => "https://gitlab.com/gitlab-org/gitlab-grack.git",
+ "gitlab-shell" => "https://github.com/gitlabhq/gitlab-shell.git",
+ "nested/six" => "git://github.com/randx/six.git",
+ "six" => "git://github.com/randx/six.git"
+ })
+ end
+ end
+
describe '#commit_count' do
it { expect(repository.commit_count("master")).to eq(25) }
it { expect(repository.commit_count("feature")).to eq(9) }
diff --git a/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb b/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
new file mode 100644
index 00000000000..28056a6085d
--- /dev/null
+++ b/spec/lib/gitlab/graphql/representation/submodule_tree_entry_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Graphql::Representation::SubmoduleTreeEntry do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ describe '.decorate' do
+ let(:submodules) { repository.tree.submodules }
+
+ it 'returns array of SubmoduleTreeEntry' do
+ entries = described_class.decorate(submodules, repository.tree)
+
+ expect(entries.first).to be_a(described_class)
+
+ expect(entries.map(&:web_url)).to contain_exactly(
+ "https://gitlab.com/gitlab-org/gitlab-grack",
+ "https://github.com/gitlabhq/gitlab-shell",
+ "https://github.com/randx/six"
+ )
+
+ expect(entries.map(&:tree_url)).to contain_exactly(
+ "https://gitlab.com/gitlab-org/gitlab-grack/tree/645f6c4c82fd3f5e06f67134450a570b795e55a6",
+ "https://github.com/gitlabhq/gitlab-shell/tree/79bceae69cb5750d6567b223597999bfa91cb3b9",
+ "https://github.com/randx/six/tree/409f37c4f05865e4fb208c771485f211a22c4c2d"
+ )
+ end
+ end
+end
diff --git a/spec/lib/gitlab/zoom_link_extractor_spec.rb b/spec/lib/gitlab/zoom_link_extractor_spec.rb
new file mode 100644
index 00000000000..52387fc3688
--- /dev/null
+++ b/spec/lib/gitlab/zoom_link_extractor_spec.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::ZoomLinkExtractor do
+ describe "#links" do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:text, :links) do
+ 'issue text https://zoom.us/j/123 and https://zoom.us/s/1123433' | %w[https://zoom.us/j/123 https://zoom.us/s/1123433]
+ 'https://zoom.us/j/1123433 issue text' | %w[https://zoom.us/j/1123433]
+ 'issue https://zoom.us/my/1123433 text' | %w[https://zoom.us/my/1123433]
+ 'issue https://gitlab.com and https://gitlab.zoom.us/s/1123433' | %w[https://gitlab.zoom.us/s/1123433]
+ 'https://gitlab.zoom.us/j/1123433' | %w[https://gitlab.zoom.us/j/1123433]
+ 'https://gitlab.zoom.us/my/1123433' | %w[https://gitlab.zoom.us/my/1123433]
+ end
+
+ with_them do
+ subject { described_class.new(text).links }
+
+ it { is_expected.to eq(links) }
+ end
+ end
+end
diff --git a/spec/migrations/fix_wrong_pages_access_level_spec.rb b/spec/migrations/fix_wrong_pages_access_level_spec.rb
new file mode 100644
index 00000000000..75ac5d919b2
--- /dev/null
+++ b/spec/migrations/fix_wrong_pages_access_level_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190703185326_fix_wrong_pages_access_level.rb')
+
+describe FixWrongPagesAccessLevel, :migration, :sidekiq, schema: 20190628185004 do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:migration_class) { described_class::MIGRATION }
+ let(:migration_name) { migration_class.to_s.demodulize }
+
+ project_class = ::Gitlab::BackgroundMigration::FixPagesAccessLevel::Project
+ feature_class = ::Gitlab::BackgroundMigration::FixPagesAccessLevel::ProjectFeature
+
+ let(:namespaces_table) { table(:namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:features_table) { table(:project_features) }
+
+ let(:subgroup) do
+ root_group = namespaces_table.create(path: "group", name: "group")
+ namespaces_table.create!(path: "subgroup", name: "group", parent_id: root_group.id)
+ end
+
+ def create_project_feature(path, project_visibility, pages_access_level)
+ project = projects_table.create!(path: path, visibility_level: project_visibility,
+ namespace_id: subgroup.id)
+ features_table.create!(project_id: project.id, pages_access_level: pages_access_level)
+ end
+
+ it 'correctly schedules background migrations' do
+ Sidekiq::Testing.fake! do
+ Timecop.freeze do
+ first_id = create_project_feature("project1", project_class::PRIVATE, feature_class::PRIVATE).id
+ last_id = create_project_feature("project2", project_class::PRIVATE, feature_class::PUBLIC).id
+
+ migrate!
+
+ expect(migration_name).to be_scheduled_delayed_migration(2.minutes, first_id, last_id)
+ expect(BackgroundMigrationWorker.jobs.size).to eq(1)
+ end
+ end
+ end
+
+ def expect_migration
+ expect do
+ perform_enqueued_jobs do
+ migrate!
+ end
+ end
+ end
+
+ where(:project_visibility, :pages_access_level, :access_control_is_enabled,
+ :pages_deployed, :resulting_pages_access_level) do
+ # update settings for public projects regardless of access_control being enabled
+ project_class::PUBLIC | feature_class::PUBLIC | true | true | feature_class::ENABLED
+ project_class::PUBLIC | feature_class::PUBLIC | false | true | feature_class::ENABLED
+ # don't update public level for private and internal projects
+ project_class::PRIVATE | feature_class::PUBLIC | true | true | feature_class::PUBLIC
+ project_class::INTERNAL | feature_class::PUBLIC | true | true | feature_class::PUBLIC
+
+ # if access control is disabled but pages are deployed we make them public
+ project_class::INTERNAL | feature_class::ENABLED | false | true | feature_class::PUBLIC
+ # don't change anything if one of the conditions is not satisfied
+ project_class::INTERNAL | feature_class::ENABLED | true | true | feature_class::ENABLED
+ project_class::INTERNAL | feature_class::ENABLED | true | false | feature_class::ENABLED
+
+ # private projects
+ # if access control is enabled update pages_access_level to private regardless of deployment
+ project_class::PRIVATE | feature_class::ENABLED | true | true | feature_class::PRIVATE
+ project_class::PRIVATE | feature_class::ENABLED | true | false | feature_class::PRIVATE
+ # if access control is disabled and pages are deployed update pages_access_level to public
+ project_class::PRIVATE | feature_class::ENABLED | false | true | feature_class::PUBLIC
+ # if access control is disabled but pages aren't deployed update pages_access_level to private
+ project_class::PRIVATE | feature_class::ENABLED | false | false | feature_class::PRIVATE
+ end
+
+ with_them do
+ let!(:project_feature) do
+ create_project_feature("projectpath", project_visibility, pages_access_level)
+ end
+
+ before do
+ tested_path = File.join(Settings.pages.path, "group/subgroup/projectpath", "public")
+ allow(Dir).to receive(:exist?).with(tested_path).and_return(pages_deployed)
+
+ stub_pages_setting(access_control: access_control_is_enabled)
+ end
+
+ it "sets proper pages_access_level" do
+ expect(project_feature.reload.pages_access_level).to eq(pages_access_level)
+
+ perform_enqueued_jobs do
+ migrate!
+ end
+
+ expect(project_feature.reload.pages_access_level).to eq(resulting_pages_access_level)
+ end
+ end
+end
diff --git a/spec/models/active_session_spec.rb b/spec/models/active_session_spec.rb
index 2762eaeccd3..09c2878663a 100644
--- a/spec/models/active_session_spec.rb
+++ b/spec/models/active_session_spec.rb
@@ -132,6 +132,19 @@ RSpec.describe ActiveSession, :clean_gitlab_redis_shared_state do
expect(ActiveSession.sessions_from_ids([])).to eq([])
end
+
+ it 'uses redis lookup in batches' do
+ stub_const('ActiveSession::SESSION_BATCH_SIZE', 1)
+
+ redis = double(:redis)
+ expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis)
+
+ sessions = ['session-a', 'session-b']
+ mget_responses = sessions.map { |session| [Marshal.dump(session)]}
+ expect(redis).to receive(:mget).twice.and_return(*mget_responses)
+
+ expect(ActiveSession.sessions_from_ids([1, 2])).to eql(sessions)
+ end
end
describe '.set' do
diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb
index 50c9d5968ac..31e55bf6be6 100644
--- a/spec/models/project_feature_spec.rb
+++ b/spec/models/project_feature_spec.rb
@@ -150,4 +150,32 @@ describe ProjectFeature do
end
end
end
+
+ describe 'default pages access level' do
+ subject { project.project_feature.pages_access_level }
+
+ before do
+ # project factory overrides all values in project_feature after creation
+ project.project_feature.destroy!
+ project.build_project_feature.save!
+ end
+
+ context 'when new project is private' do
+ let(:project) { create(:project, :private) }
+
+ it { is_expected.to eq(ProjectFeature::PRIVATE) }
+ end
+
+ context 'when new project is internal' do
+ let(:project) { create(:project, :internal) }
+
+ it { is_expected.to eq(ProjectFeature::PRIVATE) }
+ end
+
+ context 'when new project is public' do
+ let(:project) { create(:project, :public) }
+
+ it { is_expected.to eq(ProjectFeature::ENABLED) }
+ end
+ end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index a2aae257352..fee300e9d7a 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -46,8 +46,6 @@ shared_examples 'languages and percentages JSON response' do
end
describe API::Projects do
- include ExternalAuthorizationServiceHelpers
-
let(:user) { create(:user) }
let(:user2) { create(:user) }
let(:user3) { create(:user) }
@@ -1425,39 +1423,6 @@ describe API::Projects do
end
end
end
-
- context 'with external authorization' do
- let(:project) do
- create(:project,
- namespace: user.namespace,
- external_authorization_classification_label: 'the-label')
- end
-
- context 'when the user has access to the project' do
- before do
- external_service_allow_access(user, project)
- end
-
- it 'includes the label in the response' do
- get api("/projects/#{project.id}", user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response['external_authorization_classification_label']).to eq('the-label')
- end
- end
-
- context 'when the external service denies access' do
- before do
- external_service_deny_access(user, project)
- end
-
- it 'returns a 404' do
- get api("/projects/#{project.id}", user)
-
- expect(response).to have_gitlab_http_status(404)
- end
- end
- end
end
describe 'GET /projects/:id/users' do
@@ -2061,20 +2026,6 @@ describe API::Projects do
expect(response).to have_gitlab_http_status(403)
end
end
-
- context 'when updating external classification' do
- before do
- enable_external_authorization_service_check
- end
-
- it 'updates the classification label' do
- put(api("/projects/#{project.id}", user), params: { external_authorization_classification_label: 'new label' })
-
- expect(response).to have_gitlab_http_status(200)
-
- expect(project.reload.external_authorization_classification_label).to eq('new label')
- end
- end
end
describe 'POST /projects/:id/archive' do
diff --git a/spec/serializers/diff_file_base_entity_spec.rb b/spec/serializers/diff_file_base_entity_spec.rb
new file mode 100644
index 00000000000..68c5c665ed6
--- /dev/null
+++ b/spec/serializers/diff_file_base_entity_spec.rb
@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DiffFileBaseEntity do
+ let(:project) { create(:project, :repository) }
+ let(:repository) { project.repository }
+
+ context 'diff for a changed submodule' do
+ let(:commit_sha_with_changed_submodule) do
+ "cfe32cf61b73a0d5e9f13e774abde7ff789b1660"
+ end
+ let(:commit) { project.commit(commit_sha_with_changed_submodule) }
+ let(:diff_file) { commit.diffs.diff_files.to_a.last }
+ let(:options) { { request: {}, submodule_links: Gitlab::SubmoduleLinks.new(repository) } }
+ let(:entity) { described_class.new(diff_file, options).as_json }
+
+ it do
+ expect(entity[:submodule]).to eq(true)
+ expect(entity[:submodule_link]).to eq("https://github.com/randx/six")
+ expect(entity[:submodule_tree_url]).to eq(
+ "https://github.com/randx/six/tree/409f37c4f05865e4fb208c771485f211a22c4c2d"
+ )
+ end
+ end
+end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index c372a3f0e49..049702be1f6 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -65,6 +65,10 @@ module StubConfiguration
allow(Gitlab.config.artifacts).to receive_messages(to_settings(messages))
end
+ def stub_pages_setting(messages)
+ allow(Gitlab.config.pages).to receive_messages(to_settings(messages))
+ end
+
def stub_storage_settings(messages)
messages.deep_stringify_keys!