diff options
author | Dietrich Stein <dstein@gitlab.com> | 2019-07-17 13:10:23 -0400 |
---|---|---|
committer | Dietrich Stein <dstein@gitlab.com> | 2019-07-17 13:10:23 -0400 |
commit | c6a2c8f673da4e4e32b15a5b563b25b474bcc263 (patch) | |
tree | 739819c3f9b61b35e0097835ce67fab6dc50e218 | |
parent | 9bd4025b21f3ba65cde7755457111db799ef6744 (diff) | |
parent | 2860cfaaff0a435792081b72a5f97de85e0d4f70 (diff) | |
download | gitlab-ce-ds-charts-whitespace.tar.gz |
Merge branch 'master' into ds-charts-whitespaceds-charts-whitespace
118 files changed, 1350 insertions, 479 deletions
@@ -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 Binary files differnew file mode 100644 index 00000000000..5ab63074e12 --- /dev/null +++ b/doc/administration/pages/img/lets_encrypt_integration_v12_1.png 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 Binary files differnew file mode 100755 index 00000000000..9c6fa923b80 --- /dev/null +++ b/doc/user/project/img/autocomplete_characters_example1_v12_0.png diff --git a/doc/user/project/img/autocomplete_characters_example2_v12_0.png b/doc/user/project/img/autocomplete_characters_example2_v12_0.png Binary files differnew file mode 100755 index 00000000000..b2e8a782a0b --- /dev/null +++ b/doc/user/project/img/autocomplete_characters_example2_v12_0.png 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 Binary files differnew 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 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! |