summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDietrich Stein <dstein@gitlab.com>2019-07-17 13:10:23 -0400
committerDietrich Stein <dstein@gitlab.com>2019-07-17 13:10:23 -0400
commitc6a2c8f673da4e4e32b15a5b563b25b474bcc263 (patch)
tree739819c3f9b61b35e0097835ce67fab6dc50e218
parent9bd4025b21f3ba65cde7755457111db799ef6744 (diff)
parent2860cfaaff0a435792081b72a5f97de85e0d4f70 (diff)
downloadgitlab-ce-ds-charts-whitespace.tar.gz
Merge branch 'master' into ds-charts-whitespaceds-charts-whitespace
-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/framework/animations.scss2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss128
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss17
-rw-r--r--app/assets/stylesheets/framework/variables.scss4
-rw-r--r--app/assets/stylesheets/pages/commits.scss4
-rw-r--r--app/assets/stylesheets/pages/issuable.scss4
-rw-r--r--app/assets/stylesheets/pages/issues.scss1
-rw-r--r--app/assets/stylesheets/pages/labels.scss4
-rw-r--r--app/assets/stylesheets/pages/note_form.scss1
-rw-r--r--app/assets/stylesheets/pages/projects.scss5
-rw-r--r--app/assets/stylesheets/pages/tree.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/dropdowns_helper.rb2
-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/ci/pipeline_schedule.rb3
-rw-r--r--app/models/ci/trigger.rb3
-rw-r--r--app/models/concerns/deployment_platform.rb23
-rw-r--r--app/models/issue.rb3
-rw-r--r--app/models/merge_request.rb5
-rw-r--r--app/models/namespace.rb3
-rw-r--r--app/models/project.rb3
-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/issues/import_csv/_button.html.haml2
-rw-r--r--app/views/projects/pages_domains/_form.html.haml2
-rw-r--r--app/views/shared/issuable/_feed_buttons.html.haml8
-rw-r--r--changelogs/unreleased/48771-label-picker-line-break-on-long-label-titles.yml5
-rw-r--r--changelogs/unreleased/61145-fix-button-dimensions.yml5
-rw-r--r--changelogs/unreleased/64315-mget_sessions_in_chunks.yml5
-rw-r--r--changelogs/unreleased/64645-asciidoctor-preserve-footnote-link-ids.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--config/initializers/zz_metrics.rb2
-rw-r--r--danger/metadata/Dangerfile12
-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/geo/disaster_recovery/background_verification.md17
-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/api/users.md24
-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/api/users.rb8
-rw-r--r--lib/banzai/filter/ascii_doc_sanitization_filter.rb33
-rw-r--r--lib/banzai/filter/reference_redactor_filter.rb (renamed from lib/banzai/filter/redactor_filter.rb)4
-rw-r--r--lib/banzai/object_renderer.rb2
-rw-r--r--lib/banzai/pipeline/post_process_pipeline.rb2
-rw-r--r--lib/banzai/reference_redactor.rb (renamed from lib/banzai/redactor.rb)2
-rw-r--r--lib/banzai/renderer.rb2
-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--lib/tasks/gitlab/features.rake14
-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/banzai/filter/reference_redactor_filter_spec.rb (renamed from spec/lib/banzai/filter/redactor_filter_spec.rb)2
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb8
-rw-r--r--spec/lib/banzai/reference_redactor_spec.rb (renamed from spec/lib/banzai/redactor_spec.rb)2
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb50
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb246
-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/concerns/deployment_platform_spec.rb18
-rw-r--r--spec/models/project_feature_spec.rb28
-rw-r--r--spec/requests/api/projects_spec.rb49
-rw-r--r--spec/requests/api/users_spec.rb8
-rw-r--r--spec/serializers/diff_file_base_entity_spec.rb26
-rw-r--r--spec/support/features/rss_shared_examples.rb4
-rw-r--r--spec/support/helpers/stub_configuration.rb4
118 files changed, 1350 insertions, 479 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/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 6bc5632365f..6f5a2e561af 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -104,7 +104,7 @@
}
.btn {
- @include transition(border-color);
+ @include transition(background-color, border-color, color, box-shadow);
}
.dropdown-menu-toggle,
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index e0b6da31261..767832e242c 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -24,11 +24,12 @@
border-radius: $border-radius-default;
font-size: $gl-font-size;
font-weight: $gl-font-weight-normal;
- padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding;
+ padding: $gl-vert-padding $gl-btn-padding;
&:focus,
&:active {
background-color: $btn-active-gray;
+ box-shadow: $gl-btn-active-background;
}
}
@@ -49,89 +50,77 @@
color: $text;
}
- &:not(:disabled):not(.disabled) {
- &:hover {
- box-shadow: inset 0 0 0 1px $hover-border, 0 2px 2px 0 $gl-btn-hover-shadow-light;
- }
+ &:hover,
+ &:focus {
+ background-color: $hover-background;
+ border-color: $hover-border;
+ color: $hover-text;
- &:focus {
- box-shadow: inset 0 0 0 1px $hover-border, 0 0 4px 1px $blue-300;
+ > .icon {
+ color: $hover-text;
}
+ }
- &:hover,
- &:focus {
- background-color: $hover-background;
- border-color: $hover-border;
- color: $hover-text;
+ &:focus {
+ box-shadow: 0 0 4px 1px $blue-300;
+ }
- > .icon {
- color: $hover-text;
- }
- }
+ &:active {
+ background-color: $active-background;
+ border-color: $active-border;
+ box-shadow: inset 0 2px 4px 0 rgba($black, 0.2);
+ color: $active-text;
- &:active,
- &:active:focus {
- background-color: $active-background;
- border-color: $active-border;
- box-shadow: inset 0 0 0 1px $hover-border, inset 0 2px 4px 0 rgba($black, 0.2);
+ > .icon {
color: $active-text;
+ }
- > .icon {
- color: $active-text;
- }
+ &:focus {
+ box-shadow: inset 0 2px 4px 0 rgba($black, 0.2);
}
}
}
-@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color, $hover-shadow-color: $gl-btn-hover-shadow-dark) {
+@mixin btn-color($light, $border-light, $normal, $border-normal, $dark, $border-dark, $color) {
background-color: $light;
border-color: $border-light;
color: $color;
- &:not(:disabled):not(.disabled) {
- &:hover {
- box-shadow: inset 0 0 0 1px $border-normal, 0 2px 2px 0 $hover-shadow-color;
- }
-
- &:focus {
- box-shadow: inset 0 0 0 1px $border-normal, 0 0 4px 1px $blue-300;
- }
+ &:hover,
+ &:focus {
+ background-color: $normal;
+ border-color: $border-normal;
+ color: $color;
+ }
- &:hover,
- &:focus {
- background-color: $normal;
- border-color: $border-normal;
- color: $color;
- }
+ &:active,
+ &.active {
+ box-shadow: $gl-btn-active-background;
- &:active,
- &.active {
- box-shadow: inset 0 2px 4px 0 $gl-btn-hover-shadow-dark;
- background-color: $dark;
- border-color: $border-dark;
- color: $color;
- }
+ background-color: $dark;
+ border-color: $border-dark;
+ color: $color;
}
}
@mixin btn-green {
- @include btn-color($green-500, $green-600, $green-500, $green-700, $green-600, $green-800, $white-light);
+ @include btn-color($green-500, $green-600, $green-600, $green-700, $green-700, $green-800, $white-light);
}
@mixin btn-blue {
- @include btn-color($blue-500, $blue-600, $blue-500, $blue-700, $blue-600, $blue-800, $white-light);
+ @include btn-color($blue-500, $blue-600, $blue-600, $blue-700, $blue-700, $blue-800, $white-light);
}
@mixin btn-orange {
- @include btn-color($orange-500, $orange-600, $orange-500, $orange-700, $orange-600, $orange-800, $white-light);
+ @include btn-color($orange-500, $orange-600, $orange-600, $orange-700, $orange-700, $orange-800, $white-light);
}
@mixin btn-red {
- @include btn-color($red-500, $red-600, $red-500, $red-700, $red-600, $red-800, $white-light);
+ @include btn-color($red-500, $red-600, $red-600, $red-700, $red-700, $red-800, $white-light);
}
@mixin btn-white {
- @include btn-color($white-light, $gray-400, $gray-200, $gray-400, $gl-gray-200, $gray-500, $gl-text-color, $gl-btn-hover-shadow-light);
+ @include btn-color($white-light, $border-color, $white-normal, $border-white-normal, $white-dark, $border-gray-dark, $gl-text-color);
}
@mixin btn-with-margin {
@@ -160,20 +149,21 @@
color: $gl-text-color;
white-space: nowrap;
- line-height: $gl-btn-line-height;
&:focus:active {
outline: 0;
}
- &.btn-xs {
- font-size: $gl-btn-xs-font-size;
- line-height: $gl-btn-xs-line-height;
+ &.btn-sm {
+ padding: 4px 10px;
+ font-size: $gl-btn-small-font-size;
+ line-height: $gl-btn-small-line-height;
}
- &.btn-sm,
&.btn-xs {
- padding: 3px $gl-bordered-btn-vert-padding;
+ padding: 2px $gl-btn-padding;
+ font-size: $gl-btn-xs-font-size;
+ line-height: $gl-btn-xs-line-height;
}
&.btn-success,
@@ -249,7 +239,7 @@
&.dropdown-toggle {
.fa-caret-down {
- margin: 0;
+ margin-left: 3px;
}
}
@@ -282,7 +272,10 @@
}
svg {
- @include btn-svg;
+ height: 15px;
+ width: 15px;
+ position: relative;
+ top: 2px;
}
svg,
@@ -337,12 +330,6 @@
&.btn-grouped {
@include btn-with-margin;
}
-
- .btn {
- border-radius: $border-radius-default;
- font-size: $gl-font-size;
- line-height: $gl-btn-line-height;
- }
}
.btn-clipboard {
@@ -500,25 +487,18 @@
&:active,
&:focus {
color: $gl-text-color-secondary;
- border: 1px solid $border-gray-normal-dashed;
background-color: $white-normal;
}
}
-.btn-svg {
- padding: $gl-bordered-btn-vert-padding;
-
- svg {
- @include btn-svg;
- display: block;
- }
+.btn-svg svg {
+ @include btn-svg;
}
// All disabled buttons, regardless of color, type, etc
%disabled {
background-color: $gray-light !important;
border-color: $gray-200 !important;
- box-shadow: none;
color: $gl-text-color-disabled !important;
opacity: 1 !important;
cursor: default !important;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index 05afcecca05..29f63e9578d 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -8,6 +8,12 @@
}
}
+@mixin chevron-active {
+ .fa-chevron-down {
+ color: $gray-darkest;
+ }
+}
+
@mixin set-visible {
transform: translateY(0);
display: block;
@@ -43,6 +49,7 @@
.dropdown-toggle,
.dropdown-menu-toggle {
+ @include chevron-active;
border-color: $gray-darkest;
}
@@ -58,12 +65,12 @@
.dropdown-toggle,
.confidential-merge-request-fork-group .dropdown-toggle {
- padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding;
+ padding: 6px 8px 6px 10px;
background-color: $white-light;
color: $gl-text-color;
font-size: 14px;
- line-height: $gl-btn-line-height;
text-align: left;
+ border: 1px solid $border-color;
border-radius: $border-radius-base;
white-space: nowrap;
@@ -96,6 +103,10 @@
padding-right: 25px;
}
+ .fa {
+ color: $gray-darkest;
+ }
+
.fa-chevron-down {
font-size: $dropdown-chevron-size;
position: relative;
@@ -104,10 +115,12 @@
}
&:hover {
+ @include chevron-active;
border-color: $gray-darkest;
}
&:focus:active {
+ @include chevron-active;
border-color: $dropdown-toggle-active-border-color;
outline: 0;
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 047a9799c3f..c108f45622f 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -405,8 +405,6 @@ $tanuki-yellow: #fca326;
*/
$green-500-focus: rgba($green-500, 0.4);
$gl-btn-active-background: rgba(0, 0, 0, 0.16);
-$gl-btn-hover-shadow-dark: rgba($black, 0.2);
-$gl-btn-hover-shadow-light: rgba($black, 0.1);
$gl-btn-active-gradient: inset 0 2px 3px $gl-btn-active-background;
/*
@@ -483,8 +481,6 @@ $gl-btn-padding: 10px;
$gl-btn-line-height: 16px;
$gl-btn-vert-padding: 8px;
$gl-btn-horz-padding: 12px;
-$gl-bordered-btn-vert-padding: $gl-btn-vert-padding - 1px;
-$gl-bordered-btn-horz-padding: $gl-btn-horz-padding - 1px;
$gl-btn-small-font-size: 13px;
$gl-btn-small-line-height: 18px;
$gl-btn-xs-font-size: 13px;
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index 3d11aa58871..0b0a4e50146 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -214,10 +214,10 @@
.label,
.btn {
- padding: $gl-bordered-btn-vert-padding $gl-bordered-btn-horz-padding;
+ padding: $gl-vert-padding $gl-btn-padding;
border: 1px $border-color solid;
font-size: $gl-font-size;
- line-height: $gl-btn-line-height;
+ line-height: $line-height-base;
border-radius: 0;
display: flex;
align-items: center;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 66ea70e79da..6a0127eb51c 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -929,6 +929,10 @@
margin: 0;
}
}
+
+ .dropdown-toggle > .icon {
+ margin: 0 3px;
+ }
}
.right-sidebar-collapsed {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index e51ca44476c..8359a60ec9f 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -267,6 +267,7 @@ ul.related-merge-requests > li {
.fa-caret-down {
pointer-events: none;
color: inherit;
+ margin-left: 0;
}
}
}
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/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 1d57a4a4784..c6bac33e888 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -417,6 +417,7 @@ table {
i {
color: $white-light;
+ padding-right: 2px;
margin-top: 2px;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 09a576c11cb..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;
}
@@ -429,7 +429,7 @@
padding: 0;
background: transparent;
border: 0;
- line-height: 2;
+ line-height: 34px;
margin: 0;
> li + li::before {
@@ -792,6 +792,7 @@
.btn {
margin-top: $gl-padding;
+ padding: $gl-btn-vert-padding $gl-btn-padding;
line-height: $gl-btn-line-height;
.icon {
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index 5c732ab0d1f..5664f46484e 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -90,7 +90,7 @@
.add-to-tree {
vertical-align: top;
- padding: $gl-bordered-btn-vert-padding;
+ padding: 8px;
svg {
top: 0;
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/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index fd94f07cc2c..64c5fae7d96 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -46,7 +46,7 @@ module DropdownsHelper
def dropdown_toggle(toggle_text, data_attr, options = {})
default_label = data_attr[:default_label]
- content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle btn #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do
+ content_tag(:button, disabled: options[:disabled], class: "dropdown-menu-toggle #{options[:toggle_class] if options.key?(:toggle_class)}", id: (options[:id] if options.key?(:id)), type: "button", data: data_attr) do
output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
output << icon('chevron-down')
output.html_safe
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/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb
index ba8cea0cea9..42d4e86fe8d 100644
--- a/app/models/ci/pipeline_schedule.rb
+++ b/app/models/ci/pipeline_schedule.rb
@@ -4,11 +4,8 @@ module Ci
class PipelineSchedule < ApplicationRecord
extend Gitlab::Ci::Model
include Importable
- include IgnorableColumn
include StripAttribute
- ignore_column :deleted_at
-
belongs_to :project
belongs_to :owner, class_name: 'User'
has_one :last_pipeline, -> { order(id: :desc) }, class_name: 'Ci::Pipeline'
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index dc9965ce08c..8792c5cf98b 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -3,11 +3,8 @@
module Ci
class Trigger < ApplicationRecord
extend Gitlab::Ci::Model
- include IgnorableColumn
include Presentable
- ignore_column :deleted_at
-
belongs_to :project
belongs_to :owner, class_name: "User"
diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb
index 8f28c897eb6..e1a8725e728 100644
--- a/app/models/concerns/deployment_platform.rb
+++ b/app/models/concerns/deployment_platform.rb
@@ -12,19 +12,10 @@ module DeploymentPlatform
private
def find_deployment_platform(environment)
- find_platform_kubernetes(environment) ||
+ find_platform_kubernetes_with_cte(environment) ||
find_instance_cluster_platform_kubernetes(environment: environment)
end
- def find_platform_kubernetes(environment)
- if Feature.enabled?(:clusters_cte)
- find_platform_kubernetes_with_cte(environment)
- else
- find_cluster_platform_kubernetes(environment: environment) ||
- find_group_cluster_platform_kubernetes(environment: environment)
- end
- end
-
# EE would override this and utilize environment argument
def find_platform_kubernetes_with_cte(_environment)
Clusters::ClustersHierarchy.new(self).base_and_ancestors
@@ -33,18 +24,6 @@ module DeploymentPlatform
end
# EE would override this and utilize environment argument
- def find_cluster_platform_kubernetes(environment: nil)
- clusters.enabled.default_environment
- .last&.platform_kubernetes
- end
-
- # EE would override this and utilize environment argument
- def find_group_cluster_platform_kubernetes(environment: nil)
- Clusters::Cluster.enabled.default_environment.ancestor_clusters_for_clusterable(self)
- .first&.platform_kubernetes
- end
-
- # EE would override this and utilize environment argument
def find_instance_cluster_platform_kubernetes(environment: nil)
Clusters::Instance.new.clusters.enabled.default_environment
.first&.platform_kubernetes
diff --git a/app/models/issue.rb b/app/models/issue.rb
index 982a94315bd..12d30389910 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -13,11 +13,8 @@ class Issue < ApplicationRecord
include RelativePositioning
include TimeTrackable
include ThrottledTouch
- include IgnorableColumn
include LabelEventable
- ignore_column :assignee_id, :branch_name, :deleted_at
-
DueDateStruct = Struct.new(:title, :name).freeze
NoDueDate = DueDateStruct.new('No Due Date', '0').freeze
AnyDueDate = DueDateStruct.new('Any Due Date', '').freeze
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index ba57fefd8f1..68e6e48fb7d 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -7,7 +7,6 @@ class MergeRequest < ApplicationRecord
include Noteable
include Referable
include Presentable
- include IgnorableColumn
include TimeTrackable
include ManualInverseAssociation
include EachBatch
@@ -24,10 +23,6 @@ class MergeRequest < ApplicationRecord
SORTING_PREFERENCE_FIELD = :merge_requests_sort
- ignore_column :locked_at,
- :ref_fetched,
- :deleted_at
-
belongs_to :target_project, class_name: "Project"
belongs_to :source_project, class_name: "Project"
belongs_to :merge_user, class_name: "User"
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index b3021fab7f1..b8d7348268a 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -8,13 +8,10 @@ class Namespace < ApplicationRecord
include AfterCommitQueue
include Storage::LegacyNamespace
include Gitlab::SQL::Pattern
- include IgnorableColumn
include FeatureGate
include FromUnion
include Gitlab::Utils::StrongMemoize
- ignore_column :deleted_at
-
# Prevent users from creating unreasonably deep level of nesting.
# The number 20 was taken based on maximum nesting level of
# Android repo (15) + some extra backup.
diff --git a/app/models/project.rb b/app/models/project.rb
index f6f7d373f91..2906aca75fc 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -31,7 +31,6 @@ class Project < ApplicationRecord
include FeatureGate
include OptionallySearch
include FromUnion
- include IgnorableColumn
extend Gitlab::Cache::RequestCache
extend Gitlab::ConfigHelper
@@ -56,8 +55,6 @@ class Project < ApplicationRecord
VALID_MIRROR_PORTS = [22, 80, 443].freeze
VALID_MIRROR_PROTOCOLS = %w(http https ssh git).freeze
- ignore_column :import_status, :import_jid, :import_error
-
cache_markdown_field :description, pipeline: :description
delegate :feature_available?, :builds_enabled?, :wiki_enabled?,
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/issues/import_csv/_button.html.haml b/app/views/projects/issues/import_csv/_button.html.haml
index 8442a53ed61..acc2c50294f 100644
--- a/app/views/projects/issues/import_csv/_button.html.haml
+++ b/app/views/projects/issues/import_csv/_button.html.haml
@@ -1,6 +1,6 @@
- type = local_assigns.fetch(:type, :icon)
-%button.csv-import-button.btn.btn-svg{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon),
+%button.csv-import-button.btn{ title: _('Import CSV'), class: ('has-tooltip' if type == :icon),
data: { toggle: 'modal', target: '.issues-import-modal' } }
- if type == :icon
= sprite_icon('upload')
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/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml
index c9506a3295c..83f60fa6fe2 100644
--- a/app/views/shared/issuable/_feed_buttons.html.haml
+++ b/app/views/shared/issuable/_feed_buttons.html.haml
@@ -1,4 +1,4 @@
-= link_to safe_params.merge(rss_url_options), class: 'btn btn-svg has-tooltip js-rss-button', data: { container: 'body' }, title: _('Subscribe to RSS feed') do
- = sprite_icon('rss')
-= link_to safe_params.merge(calendar_url_options), class: 'btn btn-svg has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do
- = sprite_icon('calendar')
+= link_to safe_params.merge(rss_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to RSS feed') do
+ = icon('rss')
+= link_to safe_params.merge(calendar_url_options), class: 'btn has-tooltip', data: { container: 'body' }, title: _('Subscribe to calendar') do
+ = custom_icon('icon_calendar')
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/61145-fix-button-dimensions.yml b/changelogs/unreleased/61145-fix-button-dimensions.yml
deleted file mode 100644
index 8f209ceaa8e..00000000000
--- a/changelogs/unreleased/61145-fix-button-dimensions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Updating button dimensions according to design spec
-merge_request: 28545
-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/64645-asciidoctor-preserve-footnote-link-ids.yml b/changelogs/unreleased/64645-asciidoctor-preserve-footnote-link-ids.yml
new file mode 100644
index 00000000000..5427a035478
--- /dev/null
+++ b/changelogs/unreleased/64645-asciidoctor-preserve-footnote-link-ids.yml
@@ -0,0 +1,5 @@
+---
+title: "Preserve footnote link ids in Asciidoctor"
+merge_request: 30790
+author: Guillaume Grossetie
+type: added \ No newline at end of file
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/config/initializers/zz_metrics.rb b/config/initializers/zz_metrics.rb
index 5aa6f73c5c5..4f309094447 100644
--- a/config/initializers/zz_metrics.rb
+++ b/config/initializers/zz_metrics.rb
@@ -53,7 +53,7 @@ def instrument_classes(instrumentation)
instrumentation.instrument_methods(Banzai::Querying)
instrumentation.instrument_instance_methods(Banzai::ObjectRenderer)
- instrumentation.instrument_instance_methods(Banzai::Redactor)
+ instrumentation.instrument_instance_methods(Banzai::ReferenceRedactor)
[Issuable, Mentionable, Participable].each do |klass|
instrumentation.instrument_instance_methods(klass)
diff --git a/danger/metadata/Dangerfile b/danger/metadata/Dangerfile
index 1adca152736..f2d68e64eb6 100644
--- a/danger/metadata/Dangerfile
+++ b/danger/metadata/Dangerfile
@@ -1,5 +1,13 @@
# rubocop:disable Style/SignalException
+THROUGHPUT_LABELS = [
+ 'Community contribution',
+ 'security',
+ 'bug',
+ 'feature',
+ 'backstage'
+].freeze
+
if gitlab.mr_body.size < 5
fail "Please provide a proper merge request description."
end
@@ -8,6 +16,10 @@ if gitlab.mr_labels.empty?
fail "Please add labels to this merge request."
end
+if (THROUGHPUT_LABELS & gitlab.mr_labels).empty?
+ warn 'Please add a [throughput label](https://about.gitlab.com/handbook/engineering/management/throughput/#implementation) to this merge request.'
+end
+
unless gitlab.mr_json["assignee"]
warn "This merge request does not have any assignee yet. Setting an assignee clarifies who needs to take action on the merge request at any given time."
end
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/geo/disaster_recovery/background_verification.md b/doc/administration/geo/disaster_recovery/background_verification.md
index 8eee9427b56..27866b7536e 100644
--- a/doc/administration/geo/disaster_recovery/background_verification.md
+++ b/doc/administration/geo/disaster_recovery/background_verification.md
@@ -171,14 +171,21 @@ If the **primary** and **secondary** nodes have a checksum verification mismatch
## Current limitations
-Until [issue #5064][ee-5064] is completed, background verification doesn't cover
-CI job artifacts and traces, LFS objects, or user uploads in file storage.
-Verify their integrity manually by following [these instructions][foreground-verification]
-on both nodes, and comparing the output between them.
+Automatic background verification doesn't cover attachments, LFS objects,
+job artifacts, and user uploads in file storage. You can keep track of the
+progress to include them in [ee-1430]. For now, you can verify their integrity
+manually by following [these instructions][foreground-verification] on both
+nodes, and comparing the output between them.
+
+In GitLab EE 12.1, Geo calculates checksums for attachments, LFS objects and
+archived traces on secondary nodes after the transfer, compares it with the
+stored checksums, and rejects transfers if mismatched. Please note that Geo
+currently does not support an automatic way to verify these data if they have
+been synced before GitLab EE 12.1.
Data in object storage is **not verified**, as the object store is responsible
for ensuring the integrity of the data.
[reset-verification]: background_verification.md#reset-verification-for-projects-where-verification-has-failed
[foreground-verification]: ../../raketasks/check.md
-[ee-5064]: https://gitlab.com/gitlab-org/gitlab-ee/issues/5064
+[ee-1430]: https://gitlab.com/groups/gitlab-org/-/epics/1430
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/api/users.md b/doc/api/users.md
index 54641f4c862..fdc84826680 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -147,6 +147,21 @@ GET /users
]
```
+Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `note` parameters.
+
+```json
+[
+ {
+ "id": 1,
+ ...
+ "shared_runners_minutes_limit": 133,
+ "extra_shared_runners_minutes_limit": 133,
+ "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123"
+ ...
+ }
+]
+```
+
Users on GitLab [Silver or higher](https://about.gitlab.com/pricing/) will also see
the `group_saml` provider option:
@@ -284,14 +299,15 @@ Example Responses:
```
Users on GitLab [Starter, Bronze, or higher](https://about.gitlab.com/pricing/) will also see
-the `shared_runners_minutes_limit` and `extra_shared_runners_minutes_limit` parameters.
+the `shared_runners_minutes_limit`, `extra_shared_runners_minutes_limit`, and `note` parameters.
```json
{
"id": 1,
"username": "john_smith",
"shared_runners_minutes_limit": 133,
- "extra_shared_runners_minutes_limit": 133
+ "extra_shared_runners_minutes_limit": 133,
+ "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123"
...
}
```
@@ -304,7 +320,8 @@ see the `group_saml` option:
"id": 1,
"username": "john_smith",
"shared_runners_minutes_limit": 133,
- "extra_shared_runners_minutes_limit": 133
+ "extra_shared_runners_minutes_limit": 133,
+ "note": "DMCA Request: 2018-11-05 | DMCA Violation | Abuse | https://gitlab.zendesk.com/agent/tickets/123"
"identities": [
{"provider": "github", "extern_uid": "2435223452345"},
{"provider": "bitbucket", "extern_uid": "john.smith"},
@@ -399,6 +416,7 @@ Parameters:
- `private_profile` (optional) - User's profile is private - true or false (default)
- `shared_runners_minutes_limit` (optional) - Pipeline minutes quota for this user **(STARTER)**
- `extra_shared_runners_minutes_limit` (optional) - Extra pipeline minutes quota for this user **(STARTER)**
+- `note` (optional) - Admin notes for this user **(STARTER)**
On password update, user will be forced to change it upon next login.
Note, at the moment this method does only return a `404` error,
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/api/users.rb b/lib/api/users.rb
index 30a278fdff1..a4ac5b629b8 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -148,7 +148,7 @@ module API
end
desc 'Create a user. Available only for admins.' do
- success Entities::UserPublic
+ success Entities::UserWithAdmin
end
params do
requires :email, type: String, desc: 'The email of the user'
@@ -168,7 +168,7 @@ module API
user = ::Users::CreateService.new(current_user, params).execute(skip_authorization: true)
if user.persisted?
- present user, with: Entities::UserPublic, current_user: current_user
+ present user, with: Entities::UserWithAdmin, current_user: current_user
else
conflict!('Email has already been taken') if User
.by_any_email(user.email.downcase)
@@ -183,7 +183,7 @@ module API
end
desc 'Update a user. Available only for admins.' do
- success Entities::UserPublic
+ success Entities::UserWithAdmin
end
params do
requires :id, type: Integer, desc: 'The ID of the user'
@@ -215,7 +215,7 @@ module API
result = ::Users::UpdateService.new(current_user, user_params.merge(user: user)).execute
if result[:status] == :success
- present user, with: Entities::UserPublic, current_user: current_user
+ present user, with: Entities::UserWithAdmin, current_user: current_user
else
render_validation_error!(user)
end
diff --git a/lib/banzai/filter/ascii_doc_sanitization_filter.rb b/lib/banzai/filter/ascii_doc_sanitization_filter.rb
index d8d63075752..9105e86ad04 100644
--- a/lib/banzai/filter/ascii_doc_sanitization_filter.rb
+++ b/lib/banzai/filter/ascii_doc_sanitization_filter.rb
@@ -8,12 +8,18 @@ module Banzai
class AsciiDocSanitizationFilter < Banzai::Filter::BaseSanitizationFilter
# Section anchor link pattern
SECTION_LINK_REF_PATTERN = /\A#{Gitlab::Asciidoc::DEFAULT_ADOC_ATTRS['idprefix']}(:?[[:alnum:]]|-|_)+\z/.freeze
+ SECTION_HEADINGS = %w(h2 h3 h4 h5 h6).freeze
+
+ # Footnote link patterns
+ FOOTNOTE_LINK_ID_PATTERNS = {
+ a: /\A_footnoteref_\d+\z/,
+ div: /\A_footnotedef_\d+\z/
+ }.freeze
# Classes used by Asciidoctor to style components
ADMONITION_CLASSES = %w(fa icon-note icon-tip icon-warning icon-caution icon-important).freeze
CALLOUT_CLASSES = ['conum'].freeze
CHECKLIST_CLASSES = %w(fa fa-check-square-o fa-square-o).freeze
-
LIST_CLASSES = %w(checklist none no-bullet unnumbered unstyled).freeze
ELEMENT_CLASSES_WHITELIST = {
@@ -26,8 +32,6 @@ module Banzai
a: ['anchor'].freeze
}.freeze
- ALLOWED_HEADERS = %w(h2 h3 h4 h5 h6).freeze
-
def customize_whitelist(whitelist)
# Allow marks
whitelist[:elements].push('mark')
@@ -44,20 +48,39 @@ module Banzai
whitelist[:transformers].push(self.class.remove_element_classes)
# Allow `id` in heading elements for section anchors
- ALLOWED_HEADERS.each do |header|
+ SECTION_HEADINGS.each do |header|
whitelist[:attributes][header] = %w(id)
end
whitelist[:transformers].push(self.class.remove_non_heading_ids)
+ # Allow `id` in footnote elements
+ FOOTNOTE_LINK_ID_PATTERNS.keys.each do |element|
+ whitelist[:attributes][element.to_s].push('id')
+ end
+ whitelist[:transformers].push(self.class.remove_non_footnote_ids)
+
whitelist
end
class << self
+ def remove_non_footnote_ids
+ lambda do |env|
+ node = env[:node]
+
+ return unless (pattern = FOOTNOTE_LINK_ID_PATTERNS[node.name.to_sym])
+ return unless node.has_attribute?('id')
+
+ return if node['id'] =~ pattern
+
+ node.remove_attribute('id')
+ end
+ end
+
def remove_non_heading_ids
lambda do |env|
node = env[:node]
- return unless ALLOWED_HEADERS.any?(node.name)
+ return unless SECTION_HEADINGS.any?(node.name)
return unless node.has_attribute?('id')
return if node['id'] =~ SECTION_LINK_REF_PATTERN
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/reference_redactor_filter.rb
index 1f091f594f8..485d3fd5fc7 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/reference_redactor_filter.rb
@@ -7,12 +7,12 @@ module Banzai
#
# Expected to be run in its own post-processing pipeline.
#
- class RedactorFilter < HTML::Pipeline::Filter
+ class ReferenceRedactorFilter < HTML::Pipeline::Filter
def call
unless context[:skip_redaction]
context = RenderContext.new(project, current_user)
- Redactor.new(context).redact([doc])
+ ReferenceRedactor.new(context).redact([doc])
end
doc
diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb
index 75661ffa233..d6d29f4bfab 100644
--- a/lib/banzai/object_renderer.rb
+++ b/lib/banzai/object_renderer.rb
@@ -72,7 +72,7 @@ module Banzai
#
# Returns an Array containing the redacted documents.
def redact_documents(documents)
- redactor = Redactor.new(context)
+ redactor = ReferenceRedactor.new(context)
redactor.redact(documents)
end
diff --git a/lib/banzai/pipeline/post_process_pipeline.rb b/lib/banzai/pipeline/post_process_pipeline.rb
index 5c199453638..54af26b41be 100644
--- a/lib/banzai/pipeline/post_process_pipeline.rb
+++ b/lib/banzai/pipeline/post_process_pipeline.rb
@@ -12,7 +12,7 @@ module Banzai
def self.internal_link_filters
[
- Filter::RedactorFilter,
+ Filter::ReferenceRedactorFilter,
Filter::InlineMetricsRedactorFilter,
Filter::RelativeLinkFilter,
Filter::IssuableStateFilter,
diff --git a/lib/banzai/redactor.rb b/lib/banzai/reference_redactor.rb
index c2da7fec7cc..eb5c35da375 100644
--- a/lib/banzai/redactor.rb
+++ b/lib/banzai/reference_redactor.rb
@@ -3,7 +3,7 @@
module Banzai
# Class for removing Markdown references a certain user is not allowed to
# view.
- class Redactor
+ class ReferenceRedactor
attr_reader :context
# context - An instance of `Banzai::RenderContext`.
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index 81f32ef5bcf..3cb9ec21e8f 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -134,7 +134,7 @@ module Banzai
#
# This method is used to perform state-dependent changes to a String of
# HTML, such as removing references that the current user doesn't have
- # permission to make (`RedactorFilter`).
+ # permission to make (`ReferenceRedactorFilter`).
#
# html - String to process
# context - Hash of options to customize output
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/lib/tasks/gitlab/features.rake b/lib/tasks/gitlab/features.rake
index d88bcca0819..9cf568c07fe 100644
--- a/lib/tasks/gitlab/features.rake
+++ b/lib/tasks/gitlab/features.rake
@@ -10,14 +10,22 @@ namespace :gitlab do
set_rugged_feature_flags(false)
puts 'All Rugged feature flags were disabled.'
end
+
+ task unset_rugged: :environment do
+ set_rugged_feature_flags(nil)
+ puts 'All Rugged feature flags were unset.'
+ end
end
def set_rugged_feature_flags(status)
Gitlab::Git::RuggedImpl::Repository::FEATURE_FLAGS.each do |flag|
- if status
- Feature.enable(flag)
- else
+ case status
+ when nil
Feature.get(flag).remove
+ when true
+ Feature.enable(flag)
+ when false
+ Feature.disable(flag)
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/banzai/filter/redactor_filter_spec.rb b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
index 919825a6102..e87440895e0 100644
--- a/spec/lib/banzai/filter/redactor_filter_spec.rb
+++ b/spec/lib/banzai/filter/reference_redactor_filter_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Filter::RedactorFilter do
+describe Banzai::Filter::ReferenceRedactorFilter do
include ActionView::Helpers::UrlHelper
include FilterSpecHelper
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 7b855251a74..e3e6e22568c 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -22,8 +22,8 @@ describe Banzai::ObjectRenderer do
expect(object.user_visible_reference_count).to eq 0
end
- it 'calls Banzai::Redactor to perform redaction' do
- expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
+ it 'calls Banzai::ReferenceRedactor to perform redaction' do
+ expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original
renderer.render([object], :note)
end
@@ -82,8 +82,8 @@ describe Banzai::ObjectRenderer do
expect(cacheless_thing.redacted_title_html).to eq("Merge branch 'branch-merged' into 'master'")
end
- it 'calls Banzai::Redactor to perform redaction' do
- expect_any_instance_of(Banzai::Redactor).to receive(:redact).and_call_original
+ it 'calls Banzai::ReferenceRedactor to perform redaction' do
+ expect_any_instance_of(Banzai::ReferenceRedactor).to receive(:redact).and_call_original
renderer.render([cacheless_thing], :title)
end
diff --git a/spec/lib/banzai/redactor_spec.rb b/spec/lib/banzai/reference_redactor_spec.rb
index 718649e0e10..a3b47c4d826 100644
--- a/spec/lib/banzai/redactor_spec.rb
+++ b/spec/lib/banzai/reference_redactor_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Banzai::Redactor do
+describe Banzai::ReferenceRedactor do
let(:user) { create(:user) }
let(:project) { build(:project) }
let(:redactor) { described_class.new(Banzai::RenderContext.new(project, user)) }
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 5293732ead1..cbd4a509a55 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -110,6 +110,56 @@ module Gitlab
expect(render(input, context)).to include(output.strip)
end
+
+ it 'removes non footnote def ids' do
+ input = <<~ADOC
+ ++++
+ <div id="def">Footnote definition</div>
+ ++++
+ ADOC
+
+ output = <<~HTML
+ <div>Footnote definition</div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+
+ it 'removes non footnote ref ids' do
+ input = <<~ADOC
+ ++++
+ <a id="ref">Footnote reference</a>
+ ++++
+ ADOC
+
+ output = <<~HTML
+ <a>Footnote reference</a>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
+ end
+
+ context 'with footnotes' do
+ it 'preserves ids and links' do
+ input = <<~ADOC
+ This paragraph has a footnote.footnote:[This is the text of the footnote.]
+ ADOC
+
+ output = <<~HTML
+ <div>
+ <p>This paragraph has a footnote.<sup>[<a id="_footnoteref_1" href="#_footnotedef_1" title="View footnote.">1</a>]</sup></p>
+ </div>
+ <div>
+ <hr>
+ <div id="_footnotedef_1">
+ <a href="#_footnoteref_1">1</a>. This is the text of the footnote.
+ </div>
+ </div>
+ HTML
+
+ expect(render(input, context)).to include(output.strip)
+ end
end
context 'with section anchors' do
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 7991e2f48b5..46ea0d7554b 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -1,32 +1,30 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::Ci::Pipeline::Seed::Build do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
+ let(:attributes) { { name: 'rspec', ref: 'master' } }
- let(:attributes) do
- { name: 'rspec', ref: 'master' }
- end
-
- subject do
- described_class.new(pipeline, attributes)
- end
+ let(:seed_build) { described_class.new(pipeline, attributes) }
describe '#attributes' do
- it 'returns hash attributes of a build' do
- expect(subject.attributes).to be_a Hash
- expect(subject.attributes)
- .to include(:name, :project, :ref)
- end
+ subject { seed_build.attributes }
+
+ it { is_expected.to be_a(Hash) }
+ it { is_expected.to include(:name, :project, :ref) }
end
describe '#bridge?' do
+ subject { seed_build.bridge? }
+
context 'when job is a bridge' do
let(:attributes) do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
- it { is_expected.to be_bridge }
+ it { is_expected.to be_truthy }
end
context 'when trigger definition is empty' do
@@ -34,20 +32,20 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
{ name: 'rspec', ref: 'master', options: { trigger: '' } }
end
- it { is_expected.not_to be_bridge }
+ it { is_expected.to be_falsey }
end
context 'when job is not a bridge' do
- it { is_expected.not_to be_bridge }
+ it { is_expected.to be_falsey }
end
end
describe '#to_resource' do
+ subject { seed_build.to_resource }
+
context 'when job is not a bridge' do
- it 'returns a valid build resource' do
- expect(subject.to_resource).to be_a(::Ci::Build)
- expect(subject.to_resource).to be_valid
- end
+ it { is_expected.to be_a(::Ci::Build) }
+ it { is_expected.to be_valid }
end
context 'when job is a bridge' do
@@ -55,71 +53,117 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
{ name: 'rspec', ref: 'master', options: { trigger: 'my/project' } }
end
- it 'returns a valid bridge resource' do
- expect(subject.to_resource).to be_a(::Ci::Bridge)
- expect(subject.to_resource).to be_valid
- end
+ it { is_expected.to be_a(::Ci::Bridge) }
+ it { is_expected.to be_valid }
end
it 'memoizes a resource object' do
- build = subject.to_resource
-
- expect(build.object_id).to eq subject.to_resource.object_id
+ expect(subject.object_id).to eq seed_build.to_resource.object_id
end
it 'can not be persisted without explicit assignment' do
- build = subject.to_resource
-
pipeline.save!
- expect(build).not_to be_persisted
+ expect(subject).not_to be_persisted
end
end
- describe 'applying only/except policies' do
+ describe 'applying job inclusion policies' do
+ subject { seed_build }
+
context 'when no branch policy is specified' do
- let(:attributes) { { name: 'rspec' } }
+ let(:attributes) do
+ { name: 'rspec' }
+ end
it { is_expected.to be_included }
end
context 'when branch policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['deploy'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: ['deploy'] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['deploy'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: ['deploy'] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy] },
+ except: { refs: %w[deploy] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'when branch regexp policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['/^deploy$/'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[/^deploy$/] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['/^deploy$/'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[/^deploy$/] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'with both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[/^deploy$/] },
+ except: { refs: %w[/^deploy$/] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'when branch policy matches' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: %w[deploy master] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[deploy master] } }
+ end
it { is_expected.to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: %w[deploy master] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[deploy master] } }
+ end
+
+ it { is_expected.not_to be_included }
+ end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[deploy master] },
+ except: { refs: %w[deploy master] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -127,13 +171,29 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when keyword policy matches' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['branches'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[branches] } }
+ end
it { is_expected.to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['branches'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[branches] } }
+ end
+
+ it { is_expected.not_to be_included }
+ end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches] },
+ except: { refs: %w[branches] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -141,50 +201,78 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when keyword policy does not match' do
context 'when using only' do
- let(:attributes) { { name: 'rspec', only: { refs: ['tags'] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: %w[tags] } }
+ end
it { is_expected.not_to be_included }
end
context 'when using except' do
- let(:attributes) { { name: 'rspec', except: { refs: ['tags'] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: %w[tags] } }
+ end
it { is_expected.to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[tags] },
+ except: { refs: %w[tags] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'with source-keyword policy' do
using RSpec::Parameterized
- let(:pipeline) { build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source) }
+ let(:pipeline) do
+ build(:ci_empty_pipeline, ref: 'deploy', tag: false, source: source)
+ end
context 'matches' do
where(:keyword, :source) do
[
- %w(pushes push),
- %w(web web),
- %w(triggers trigger),
- %w(schedules schedule),
- %w(api api),
- %w(external external)
+ %w[pushes push],
+ %w[web web],
+ %w[triggers trigger],
+ %w[schedules schedule],
+ %w[api api],
+ %w[external external]
]
end
with_them do
context 'using an only policy' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
+ end
it { is_expected.to be_included }
end
context 'using an except policy' do
- let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
+ end
it { is_expected.not_to be_included }
end
context 'using both only and except policies' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } }
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -193,29 +281,39 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'non-matches' do
where(:keyword, :source) do
- %w(web trigger schedule api external).map { |source| ['pushes', source] } +
- %w(push trigger schedule api external).map { |source| ['web', source] } +
- %w(push web schedule api external).map { |source| ['triggers', source] } +
- %w(push web trigger api external).map { |source| ['schedules', source] } +
- %w(push web trigger schedule external).map { |source| ['api', source] } +
- %w(push web trigger schedule api).map { |source| ['external', source] }
+ %w[web trigger schedule api external].map { |source| ['pushes', source] } +
+ %w[push trigger schedule api external].map { |source| ['web', source] } +
+ %w[push web schedule api external].map { |source| ['triggers', source] } +
+ %w[push web trigger api external].map { |source| ['schedules', source] } +
+ %w[push web trigger schedule external].map { |source| ['api', source] } +
+ %w[push web trigger schedule api].map { |source| ['external', source] }
end
with_them do
context 'using an only policy' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', only: { refs: [keyword] } }
+ end
it { is_expected.not_to be_included }
end
context 'using an except policy' do
- let(:attributes) { { name: 'rspec', except: { refs: [keyword] } } }
+ let(:attributes) do
+ { name: 'rspec', except: { refs: [keyword] } }
+ end
it { is_expected.to be_included }
end
context 'using both only and except policies' do
- let(:attributes) { { name: 'rspec', only: { refs: [keyword] }, except: { refs: [keyword] } } }
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: [keyword] },
+ except: { refs: [keyword] }
+ }
+ end
it { is_expected.not_to be_included }
end
@@ -239,12 +337,24 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
it { is_expected.not_to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: ["branches@#{pipeline.project_full_path}"] },
+ except: { refs: ["branches@#{pipeline.project_full_path}"] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ end
end
context 'when repository path does not matches' do
context 'when using only' do
let(:attributes) do
- { name: 'rspec', only: { refs: ['branches@fork'] } }
+ { name: 'rspec', only: { refs: %w[branches@fork] } }
end
it { is_expected.not_to be_included }
@@ -252,11 +362,23 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
context 'when using except' do
let(:attributes) do
- { name: 'rspec', except: { refs: ['branches@fork'] } }
+ { name: 'rspec', except: { refs: %w[branches@fork] } }
end
it { is_expected.to be_included }
end
+
+ context 'when using both only and except policies' do
+ let(:attributes) do
+ {
+ name: 'rspec',
+ only: { refs: %w[branches@fork] },
+ except: { refs: %w[branches@fork] }
+ }
+ end
+
+ it { is_expected.not_to be_included }
+ 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/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb
index e2fc8a5d127..2378f400540 100644
--- a/spec/models/concerns/deployment_platform_spec.rb
+++ b/spec/models/concerns/deployment_platform_spec.rb
@@ -5,7 +5,7 @@ require 'rails_helper'
describe DeploymentPlatform do
let(:project) { create(:project) }
- shared_examples '#deployment_platform' do
+ describe '#deployment_platform' do
subject { project.deployment_platform }
context 'with no Kubernetes configuration on CI/CD, no Kubernetes Service' do
@@ -84,20 +84,4 @@ describe DeploymentPlatform do
end
end
end
-
- context 'legacy implementation' do
- before do
- stub_feature_flags(clusters_cte: false)
- end
-
- include_examples '#deployment_platform'
- end
-
- context 'CTE implementation' do
- before do
- stub_feature_flags(clusters_cte: true)
- end
-
- include_examples '#deployment_platform'
- end
end
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/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 0ad50e5347a..af2bee4563a 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -448,6 +448,7 @@ describe API::Users do
it "returns 201 Created on success" do
post api("/users", admin), params: attributes_for(:user, projects_limit: 3)
+ expect(response).to match_response_schema('public_api/v4/user/admin')
expect(response).to have_gitlab_http_status(201)
end
@@ -643,6 +644,13 @@ describe API::Users do
describe "PUT /users/:id" do
let!(:admin_user) { create(:admin) }
+ it "returns 200 OK on success" do
+ put api("/users/#{user.id}", admin), params: { bio: 'new test bio' }
+
+ expect(response).to match_response_schema('public_api/v4/user/admin')
+ expect(response).to have_gitlab_http_status(200)
+ end
+
it "updates user with new bio" do
put api("/users/#{user.id}", admin), params: { bio: 'new test bio' }
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/features/rss_shared_examples.rb b/spec/support/features/rss_shared_examples.rb
index 02d310a9afa..0de92aedba5 100644
--- a/spec/support/features/rss_shared_examples.rb
+++ b/spec/support/features/rss_shared_examples.rb
@@ -6,7 +6,7 @@ end
shared_examples "it has an RSS button with current_user's feed token" do
it "shows the RSS button with current_user's feed token" do
- expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}'], .js-rss-button[href*='feed_token=#{user.feed_token}']")
+ expect(page).to have_css("a:has(.fa-rss)[href*='feed_token=#{user.feed_token}']")
end
end
@@ -18,6 +18,6 @@ end
shared_examples "it has an RSS button without a feed token" do
it "shows the RSS button without a feed token" do
- expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token']), .js-rss-button:not([href*='feed_token'])")
+ expect(page).to have_css("a:has(.fa-rss):not([href*='feed_token'])")
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!