Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									7b78125a38
								
							
						
					
					
						commit
						ef615776bf
					
				|  | @ -519,10 +519,26 @@ rspec:coverage: | ||||||
|     paths: |     paths: | ||||||
|       - coverage/index.html |       - coverage/index.html | ||||||
|       - coverage/assets/ |       - coverage/assets/ | ||||||
|  |       - coverage/lcov/ | ||||||
|       - tmp/memory_test/ |       - tmp/memory_test/ | ||||||
|     reports: |     reports: | ||||||
|       cobertura: coverage/coverage.xml |       cobertura: coverage/coverage.xml | ||||||
| 
 | 
 | ||||||
|  | rspec:undercoverage: | ||||||
|  |   extends: | ||||||
|  |     - .coverage-base | ||||||
|  |     - .rails:rules:rspec-undercoverage | ||||||
|  |   stage: post-test | ||||||
|  |   needs: ["rspec:coverage"] | ||||||
|  |   script: | ||||||
|  |     - if [ -n "$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA" ]; then | ||||||
|  |         echo "Checking out \$CI_MERGE_REQUEST_SOURCE_BRANCH_SHA ($CI_MERGE_REQUEST_SOURCE_BRANCH_SHA) instead of \$CI_COMMIT_SHA (merge result commit $CI_COMMIT_SHA) so we can use $CI_MERGE_REQUEST_DIFF_BASE_SHA for undercoverage in this merged result pipeline"; | ||||||
|  |         git checkout -f ${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}; | ||||||
|  |       else | ||||||
|  |         echo "Using \$CI_COMMIT_SHA ($CI_COMMIT_SHA) for this non-merge result pipeline."; | ||||||
|  |       fi; | ||||||
|  |     - run_timed_command "scripts/undercoverage" | ||||||
|  | 
 | ||||||
| rspec:feature-flags: | rspec:feature-flags: | ||||||
|   extends: |   extends: | ||||||
|     - .coverage-base |     - .coverage-base | ||||||
|  |  | ||||||
|  | @ -67,6 +67,9 @@ | ||||||
| .if-merge-request-labels-run-review-app: &if-merge-request-labels-run-review-app | .if-merge-request-labels-run-review-app: &if-merge-request-labels-run-review-app | ||||||
|   if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-review-app/' |   if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:run-review-app/' | ||||||
| 
 | 
 | ||||||
|  | .if-merge-request-labels-skip-undercoverage: &if-merge-request-labels-skip-undercoverage | ||||||
|  |   if: '$CI_MERGE_REQUEST_LABELS =~ /pipeline:skip-undercoverage/' | ||||||
|  | 
 | ||||||
| .if-security-merge-request: &if-security-merge-request | .if-security-merge-request: &if-security-merge-request | ||||||
|   if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID' |   if: '$CI_PROJECT_NAMESPACE == "gitlab-org/security" && $CI_MERGE_REQUEST_IID' | ||||||
| 
 | 
 | ||||||
|  | @ -1359,6 +1362,17 @@ | ||||||
|     - <<: *if-merge-request-labels-run-all-rspec |     - <<: *if-merge-request-labels-run-all-rspec | ||||||
|       when: always |       when: always | ||||||
| 
 | 
 | ||||||
|  | .rails:rules:rspec-undercoverage: | ||||||
|  |   rules: | ||||||
|  |     - <<: *if-not-ee | ||||||
|  |       when: never | ||||||
|  |     - <<: *if-merge-request-labels-skip-undercoverage | ||||||
|  |       allow_failure: true | ||||||
|  |     - <<: *if-merge-request-labels-run-all-rspec | ||||||
|  |     - <<: *if-merge-request-approved | ||||||
|  |     - <<: *if-merge-request | ||||||
|  |       changes: *backend-patterns | ||||||
|  | 
 | ||||||
| .rails:rules:default-branch-schedule-nightly--code-backstage: | .rails:rules:default-branch-schedule-nightly--code-backstage: | ||||||
|   rules: |   rules: | ||||||
|     - <<: *if-default-branch-schedule-nightly |     - <<: *if-default-branch-schedule-nightly | ||||||
|  |  | ||||||
|  | @ -1 +1 @@ | ||||||
| 4dd8bfe1307ffcc5a2a3f4eb70da7977a7c1d915 | a191a5d10f0772ae2ed6ec869001ddde6d277827 | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										2
									
								
								Gemfile
								
								
								
								
							|  | @ -405,7 +405,9 @@ end | ||||||
| 
 | 
 | ||||||
| group :development, :test, :coverage do | group :development, :test, :coverage do | ||||||
|   gem 'simplecov', '~> 0.18.5', require: false |   gem 'simplecov', '~> 0.18.5', require: false | ||||||
|  |   gem 'simplecov-lcov', '~> 0.8.0', require: false | ||||||
|   gem 'simplecov-cobertura', '~> 1.3.1', require: false |   gem 'simplecov-cobertura', '~> 1.3.1', require: false | ||||||
|  |   gem 'undercover', '~> 0.4.4', require: false | ||||||
| end | end | ||||||
| 
 | 
 | ||||||
| # Gems required in omnibus-gitlab pipeline | # Gems required in omnibus-gitlab pipeline | ||||||
|  |  | ||||||
|  | @ -645,6 +645,8 @@ GEM | ||||||
|       concurrent-ruby (~> 1.0) |       concurrent-ruby (~> 1.0) | ||||||
|     i18n_data (0.8.0) |     i18n_data (0.8.0) | ||||||
|     icalendar (2.4.1) |     icalendar (2.4.1) | ||||||
|  |     imagen (0.1.8) | ||||||
|  |       parser (>= 2.5, != 2.5.1.1) | ||||||
|     invisible_captcha (1.1.0) |     invisible_captcha (1.1.0) | ||||||
|       rails (>= 4.2) |       rails (>= 4.2) | ||||||
|     ipaddress (0.8.3) |     ipaddress (0.8.3) | ||||||
|  | @ -1197,6 +1199,7 @@ GEM | ||||||
|     simplecov-cobertura (1.3.1) |     simplecov-cobertura (1.3.1) | ||||||
|       simplecov (~> 0.8) |       simplecov (~> 0.8) | ||||||
|     simplecov-html (0.12.3) |     simplecov-html (0.12.3) | ||||||
|  |     simplecov-lcov (0.8.0) | ||||||
|     sixarm_ruby_unaccent (1.2.0) |     sixarm_ruby_unaccent (1.2.0) | ||||||
|     slack-messenger (2.3.4) |     slack-messenger (2.3.4) | ||||||
|     snowplow-tracker (0.6.1) |     snowplow-tracker (0.6.1) | ||||||
|  | @ -1312,6 +1315,10 @@ GEM | ||||||
|       concurrent-ruby (~> 1.0) |       concurrent-ruby (~> 1.0) | ||||||
|     u2f (0.2.1) |     u2f (0.2.1) | ||||||
|     uber (0.1.0) |     uber (0.1.0) | ||||||
|  |     undercover (0.4.4) | ||||||
|  |       imagen (>= 0.1.8) | ||||||
|  |       rainbow (>= 2.1, < 4.0) | ||||||
|  |       rugged (>= 0.27, < 1.3) | ||||||
|     unf (0.1.4) |     unf (0.1.4) | ||||||
|       unf_ext |       unf_ext | ||||||
|     unf_ext (0.0.7.7) |     unf_ext (0.0.7.7) | ||||||
|  | @ -1626,6 +1633,7 @@ DEPENDENCIES | ||||||
|   simple_po_parser (~> 1.1.2) |   simple_po_parser (~> 1.1.2) | ||||||
|   simplecov (~> 0.18.5) |   simplecov (~> 0.18.5) | ||||||
|   simplecov-cobertura (~> 1.3.1) |   simplecov-cobertura (~> 1.3.1) | ||||||
|  |   simplecov-lcov (~> 0.8.0) | ||||||
|   slack-messenger (~> 2.3.4) |   slack-messenger (~> 2.3.4) | ||||||
|   snowplow-tracker (~> 0.6.1) |   snowplow-tracker (~> 0.6.1) | ||||||
|   solargraph (~> 0.43) |   solargraph (~> 0.43) | ||||||
|  | @ -1648,6 +1656,7 @@ DEPENDENCIES | ||||||
|   toml-rb (~> 2.0) |   toml-rb (~> 2.0) | ||||||
|   truncato (~> 0.7.11) |   truncato (~> 0.7.11) | ||||||
|   u2f (~> 0.2.1) |   u2f (~> 0.2.1) | ||||||
|  |   undercover (~> 0.4.4) | ||||||
|   unf (~> 0.1.4) |   unf (~> 0.1.4) | ||||||
|   unleash (~> 3.2.2) |   unleash (~> 3.2.2) | ||||||
|   valid_email (~> 0.1) |   valid_email (~> 0.1) | ||||||
|  |  | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | import { mergeAttributes, Node } from '@tiptap/core'; | ||||||
|  | import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; | ||||||
|  | 
 | ||||||
|  | export default Node.create({ | ||||||
|  |   name: 'footnoteDefinition', | ||||||
|  | 
 | ||||||
|  |   content: 'paragraph', | ||||||
|  | 
 | ||||||
|  |   group: 'block', | ||||||
|  | 
 | ||||||
|  |   parseHTML() { | ||||||
|  |     return [ | ||||||
|  |       { tag: 'section.footnotes li' }, | ||||||
|  |       { tag: '.footnote-backref', priority: PARSE_HTML_PRIORITY_HIGHEST, ignore: true }, | ||||||
|  |     ]; | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   renderHTML({ HTMLAttributes }) { | ||||||
|  |     return ['li', mergeAttributes(HTMLAttributes), 0]; | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,37 @@ | ||||||
|  | import { Node, mergeAttributes } from '@tiptap/core'; | ||||||
|  | import { PARSE_HTML_PRIORITY_HIGHEST } from '../constants'; | ||||||
|  | 
 | ||||||
|  | export default Node.create({ | ||||||
|  |   name: 'footnoteReference', | ||||||
|  | 
 | ||||||
|  |   inline: true, | ||||||
|  | 
 | ||||||
|  |   group: 'inline', | ||||||
|  | 
 | ||||||
|  |   atom: true, | ||||||
|  | 
 | ||||||
|  |   draggable: true, | ||||||
|  | 
 | ||||||
|  |   selectable: true, | ||||||
|  | 
 | ||||||
|  |   addAttributes() { | ||||||
|  |     return { | ||||||
|  |       footnoteId: { | ||||||
|  |         default: null, | ||||||
|  |         parseHTML: (element) => element.querySelector('a').getAttribute('id'), | ||||||
|  |       }, | ||||||
|  |       footnoteNumber: { | ||||||
|  |         default: null, | ||||||
|  |         parseHTML: (element) => element.textContent, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   parseHTML() { | ||||||
|  |     return [{ tag: 'sup.footnote-ref', priority: PARSE_HTML_PRIORITY_HIGHEST }]; | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   renderHTML({ HTMLAttributes: { footnoteNumber, footnoteId, ...HTMLAttributes } }) { | ||||||
|  |     return ['sup', mergeAttributes(HTMLAttributes), footnoteNumber]; | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | @ -0,0 +1,19 @@ | ||||||
|  | import { mergeAttributes, Node } from '@tiptap/core'; | ||||||
|  | 
 | ||||||
|  | export default Node.create({ | ||||||
|  |   name: 'footnotesSection', | ||||||
|  | 
 | ||||||
|  |   content: 'footnoteDefinition+', | ||||||
|  | 
 | ||||||
|  |   group: 'block', | ||||||
|  | 
 | ||||||
|  |   isolating: true, | ||||||
|  | 
 | ||||||
|  |   parseHTML() { | ||||||
|  |     return [{ tag: 'section.footnotes > ol' }]; | ||||||
|  |   }, | ||||||
|  | 
 | ||||||
|  |   renderHTML({ HTMLAttributes }) { | ||||||
|  |     return ['ol', mergeAttributes(HTMLAttributes, { class: 'footnotes gl-font-sm' }), 0]; | ||||||
|  |   }, | ||||||
|  | }); | ||||||
|  | @ -19,6 +19,9 @@ import Dropcursor from '../extensions/dropcursor'; | ||||||
| import Emoji from '../extensions/emoji'; | import Emoji from '../extensions/emoji'; | ||||||
| import Figure from '../extensions/figure'; | import Figure from '../extensions/figure'; | ||||||
| import FigureCaption from '../extensions/figure_caption'; | import FigureCaption from '../extensions/figure_caption'; | ||||||
|  | import FootnoteDefinition from '../extensions/footnote_definition'; | ||||||
|  | import FootnoteReference from '../extensions/footnote_reference'; | ||||||
|  | import FootnotesSection from '../extensions/footnotes_section'; | ||||||
| import Frontmatter from '../extensions/frontmatter'; | import Frontmatter from '../extensions/frontmatter'; | ||||||
| import Gapcursor from '../extensions/gapcursor'; | import Gapcursor from '../extensions/gapcursor'; | ||||||
| import HardBreak from '../extensions/hard_break'; | import HardBreak from '../extensions/hard_break'; | ||||||
|  | @ -94,6 +97,9 @@ export const createContentEditor = ({ | ||||||
|     Emoji, |     Emoji, | ||||||
|     Figure, |     Figure, | ||||||
|     FigureCaption, |     FigureCaption, | ||||||
|  |     FootnoteDefinition, | ||||||
|  |     FootnoteReference, | ||||||
|  |     FootnotesSection, | ||||||
|     Frontmatter, |     Frontmatter, | ||||||
|     Gapcursor, |     Gapcursor, | ||||||
|     HardBreak, |     HardBreak, | ||||||
|  |  | ||||||
|  | @ -17,6 +17,9 @@ import Division from '../extensions/division'; | ||||||
| import Emoji from '../extensions/emoji'; | import Emoji from '../extensions/emoji'; | ||||||
| import Figure from '../extensions/figure'; | import Figure from '../extensions/figure'; | ||||||
| import FigureCaption from '../extensions/figure_caption'; | import FigureCaption from '../extensions/figure_caption'; | ||||||
|  | import FootnotesSection from '../extensions/footnotes_section'; | ||||||
|  | import FootnoteDefinition from '../extensions/footnote_definition'; | ||||||
|  | import FootnoteReference from '../extensions/footnote_reference'; | ||||||
| import Frontmatter from '../extensions/frontmatter'; | import Frontmatter from '../extensions/frontmatter'; | ||||||
| import HardBreak from '../extensions/hard_break'; | import HardBreak from '../extensions/hard_break'; | ||||||
| import Heading from '../extensions/heading'; | import Heading from '../extensions/heading'; | ||||||
|  | @ -156,6 +159,15 @@ const defaultSerializerConfig = { | ||||||
| 
 | 
 | ||||||
|       state.write(`:${name}:`); |       state.write(`:${name}:`); | ||||||
|     }, |     }, | ||||||
|  |     [FootnoteDefinition.name]: (state, node) => { | ||||||
|  |       state.renderInline(node); | ||||||
|  |     }, | ||||||
|  |     [FootnoteReference.name]: (state, node) => { | ||||||
|  |       state.write(`[^${node.attrs.footnoteNumber}]`); | ||||||
|  |     }, | ||||||
|  |     [FootnotesSection.name]: (state, node) => { | ||||||
|  |       state.renderList(node, '', (index) => `[^${index + 1}]: `); | ||||||
|  |     }, | ||||||
|     [Frontmatter.name]: (state, node) => { |     [Frontmatter.name]: (state, node) => { | ||||||
|       const { language } = node.attrs; |       const { language } = node.attrs; | ||||||
|       const syntax = { |       const syntax = { | ||||||
|  |  | ||||||
|  | @ -63,7 +63,7 @@ export default { | ||||||
|           class="ide-sidebar-link js-ide-review-mode" |           class="ide-sidebar-link js-ide-review-mode" | ||||||
|           @click.prevent="changedActivityView($event, $options.leftSidebarViews.review.name)" |           @click.prevent="changedActivityView($event, $options.leftSidebarViews.review.name)" | ||||||
|         > |         > | ||||||
|           <gl-icon name="file-modified" /> |           <gl-icon name="review-list" /> | ||||||
|         </button> |         </button> | ||||||
|       </li> |       </li> | ||||||
|       <li> |       <li> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,27 @@ | ||||||
|  | import InputCopyToggleVisibility from './input_copy_toggle_visibility.vue'; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   component: InputCopyToggleVisibility, | ||||||
|  |   title: 'vue_shared/components/form/input_copy_toggle_visibility', | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const defaultProps = { | ||||||
|  |   value: 'hR8x1fuJbzwu5uFKLf9e', | ||||||
|  |   formInputGroupProps: { class: 'gl-form-input-xl' }, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | const Template = (args, { argTypes }) => ({ | ||||||
|  |   components: { InputCopyToggleVisibility }, | ||||||
|  |   props: Object.keys(argTypes), | ||||||
|  |   template: `<input-copy-toggle-visibility
 | ||||||
|  |       :value="value"  | ||||||
|  |       :initial-visibility="initialVisibility" | ||||||
|  |       :show-toggle-visibility-button="showToggleVisibilityButton" | ||||||
|  |       :show-copy-button="showCopyButton" | ||||||
|  |       :form-input-group-props="formInputGroupProps" | ||||||
|  |       :copy-button-title="copyButtonTitle" | ||||||
|  |     />`, | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export const Default = Template.bind({}); | ||||||
|  | Default.args = defaultProps; | ||||||
|  | @ -0,0 +1,124 @@ | ||||||
|  | <script> | ||||||
|  | import { GlFormInputGroup, GlFormGroup, GlButton, GlTooltipDirective } from '@gitlab/ui'; | ||||||
|  | 
 | ||||||
|  | import { __ } from '~/locale'; | ||||||
|  | import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; | ||||||
|  | 
 | ||||||
|  | export default { | ||||||
|  |   name: 'InputCopyToggleVisibility', | ||||||
|  |   i18n: { | ||||||
|  |     toggleVisibilityLabelHide: __('Click to hide'), | ||||||
|  |     toggleVisibilityLabelReveal: __('Click to reveal'), | ||||||
|  |   }, | ||||||
|  |   components: { | ||||||
|  |     GlFormInputGroup, | ||||||
|  |     GlFormGroup, | ||||||
|  |     GlButton, | ||||||
|  |     ClipboardButton, | ||||||
|  |   }, | ||||||
|  |   directives: { | ||||||
|  |     GlTooltip: GlTooltipDirective, | ||||||
|  |   }, | ||||||
|  |   props: { | ||||||
|  |     value: { | ||||||
|  |       type: String, | ||||||
|  |       required: false, | ||||||
|  |       default: '', | ||||||
|  |     }, | ||||||
|  |     initialVisibility: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: false, | ||||||
|  |       default: false, | ||||||
|  |     }, | ||||||
|  |     showToggleVisibilityButton: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: false, | ||||||
|  |       default: true, | ||||||
|  |     }, | ||||||
|  |     showCopyButton: { | ||||||
|  |       type: Boolean, | ||||||
|  |       required: false, | ||||||
|  |       default: true, | ||||||
|  |     }, | ||||||
|  |     copyButtonTitle: { | ||||||
|  |       type: String, | ||||||
|  |       required: false, | ||||||
|  |       default: __('Copy'), | ||||||
|  |     }, | ||||||
|  |     formInputGroupProps: { | ||||||
|  |       type: Object, | ||||||
|  |       required: false, | ||||||
|  |       default() { | ||||||
|  |         return {}; | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   data() { | ||||||
|  |     return { | ||||||
|  |       valueIsVisible: this.initialVisibility, | ||||||
|  |     }; | ||||||
|  |   }, | ||||||
|  |   computed: { | ||||||
|  |     toggleVisibilityLabel() { | ||||||
|  |       return this.valueIsVisible | ||||||
|  |         ? this.$options.i18n.toggleVisibilityLabelHide | ||||||
|  |         : this.$options.i18n.toggleVisibilityLabelReveal; | ||||||
|  |     }, | ||||||
|  |     toggleVisibilityIcon() { | ||||||
|  |       return this.valueIsVisible ? 'eye-slash' : 'eye'; | ||||||
|  |     }, | ||||||
|  |     computedValueIsVisible() { | ||||||
|  |       return !this.showToggleVisibilityButton || this.valueIsVisible; | ||||||
|  |     }, | ||||||
|  |     displayedValue() { | ||||||
|  |       return this.computedValueIsVisible ? this.value : '*'.repeat(this.value.length || 20); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   methods: { | ||||||
|  |     handleToggleVisibilityButtonClick() { | ||||||
|  |       this.valueIsVisible = !this.valueIsVisible; | ||||||
|  | 
 | ||||||
|  |       this.$emit('visibility-change', this.valueIsVisible); | ||||||
|  |     }, | ||||||
|  |     handleCopyButtonClick() { | ||||||
|  |       this.$emit('copy'); | ||||||
|  |     }, | ||||||
|  |     handleFormInputCopy(event) { | ||||||
|  |       if (this.computedValueIsVisible) { | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       event.clipboardData.setData('text/plain', this.value); | ||||||
|  |       event.preventDefault(); | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  | }; | ||||||
|  | </script> | ||||||
|  | <template> | ||||||
|  |   <gl-form-group v-bind="$attrs"> | ||||||
|  |     <gl-form-input-group | ||||||
|  |       :value="displayedValue" | ||||||
|  |       input-class="gl-font-monospace! gl-cursor-default!" | ||||||
|  |       select-on-click | ||||||
|  |       readonly | ||||||
|  |       v-bind="formInputGroupProps" | ||||||
|  |       @copy="handleFormInputCopy" | ||||||
|  |     > | ||||||
|  |       <template v-if="showToggleVisibilityButton || showCopyButton" #append> | ||||||
|  |         <gl-button | ||||||
|  |           v-if="showToggleVisibilityButton" | ||||||
|  |           v-gl-tooltip.hover="toggleVisibilityLabel" | ||||||
|  |           :aria-label="toggleVisibilityLabel" | ||||||
|  |           :icon="toggleVisibilityIcon" | ||||||
|  |           @click="handleToggleVisibilityButtonClick" | ||||||
|  |         /> | ||||||
|  |         <clipboard-button | ||||||
|  |           v-if="showCopyButton" | ||||||
|  |           :text="value" | ||||||
|  |           :title="copyButtonTitle" | ||||||
|  |           @click="handleCopyButtonClick" | ||||||
|  |         /> | ||||||
|  |       </template> | ||||||
|  |     </gl-form-input-group> | ||||||
|  |   </gl-form-group> | ||||||
|  | </template> | ||||||
|  | @ -1084,16 +1084,6 @@ module Ci | ||||||
|       runner&.instance_type? |       runner&.instance_type? | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def job_variables_attributes |  | ||||||
|       strong_memoize(:job_variables_attributes) do |  | ||||||
|         job_variables.map do |variable| |  | ||||||
|           variable.attributes.except('id', 'job_id', 'encrypted_value', 'encrypted_value_iv').tap do |attrs| |  | ||||||
|             attrs[:value] = variable.value |  | ||||||
|           end |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
| 
 |  | ||||||
|     protected |     protected | ||||||
| 
 | 
 | ||||||
|     def run_status_commit_hooks! |     def run_status_commit_hooks! | ||||||
|  |  | ||||||
|  | @ -7,7 +7,7 @@ module Ci | ||||||
|          allow_failure stage stage_id stage_idx trigger_request |          allow_failure stage stage_id stage_idx trigger_request | ||||||
|          yaml_variables when environment coverage_regex |          yaml_variables when environment coverage_regex | ||||||
|          description tag_list protected needs_attributes |          description tag_list protected needs_attributes | ||||||
|          resource_group scheduling_type job_variables_attributes].freeze |          resource_group scheduling_type].freeze | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     def self.extra_accessors |     def self.extra_accessors | ||||||
|  |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| --- |  | ||||||
| name: drop_detached_partitions |  | ||||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67056 |  | ||||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337155 |  | ||||||
| milestone: '14.2' |  | ||||||
| type: development |  | ||||||
| group: group::database |  | ||||||
| default_enabled: false |  | ||||||
|  | @ -1,8 +0,0 @@ | ||||||
| --- |  | ||||||
| name: partition_pruning |  | ||||||
| introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/67056 |  | ||||||
| rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337153 |  | ||||||
| milestone: '14.2' |  | ||||||
| type: development |  | ||||||
| group: group::database |  | ||||||
| default_enabled: false |  | ||||||
|  | @ -10,5 +10,4 @@ link: https://docs.gitlab.com/ee/development/documentation/styleguide/index.html | ||||||
| level: suggestion | level: suggestion | ||||||
| ignorecase: true | ignorecase: true | ||||||
| swap: | swap: | ||||||
|   button: 'if possible, rewrite to not use' |   button: 'if possible, rewrite to remove' | ||||||
|   area: 'use "section" instead of' |  | ||||||
|  |  | ||||||
|  | @ -252,6 +252,7 @@ Example response: | ||||||
|   "finished_at": "2016-01-11T10:15:10.506Z", |   "finished_at": "2016-01-11T10:15:10.506Z", | ||||||
|   "duration": 97.0, |   "duration": 97.0, | ||||||
|   "status": "failed", |   "status": "failed", | ||||||
|  |   "failure_reason": "script_failure", | ||||||
|   "tag": false, |   "tag": false, | ||||||
|   "web_url": "https://example.com/foo/bar/-/jobs/42", |   "web_url": "https://example.com/foo/bar/-/jobs/42", | ||||||
|   "user": null |   "user": null | ||||||
|  |  | ||||||
|  | @ -71,6 +71,7 @@ Example of response | ||||||
|     "runner": null, |     "runner": null, | ||||||
|     "stage": "test", |     "stage": "test", | ||||||
|     "status": "failed", |     "status": "failed", | ||||||
|  |     "failure_reason": "script_failure", | ||||||
|     "tag": false, |     "tag": false, | ||||||
|     "web_url": "https://example.com/foo/bar/-/jobs/7", |     "web_url": "https://example.com/foo/bar/-/jobs/7", | ||||||
|     "user": { |     "user": { | ||||||
|  | @ -126,6 +127,7 @@ Example of response | ||||||
|     "runner": null, |     "runner": null, | ||||||
|     "stage": "test", |     "stage": "test", | ||||||
|     "status": "failed", |     "status": "failed", | ||||||
|  |     "failure_reason": "stuck_or_timeout_failure", | ||||||
|     "tag": false, |     "tag": false, | ||||||
|     "web_url": "https://example.com/foo/bar/-/jobs/6", |     "web_url": "https://example.com/foo/bar/-/jobs/6", | ||||||
|     "user": { |     "user": { | ||||||
|  | @ -207,6 +209,7 @@ Example of response | ||||||
|     "runner": null, |     "runner": null, | ||||||
|     "stage": "test", |     "stage": "test", | ||||||
|     "status": "failed", |     "status": "failed", | ||||||
|  |     "failure_reason": "stuck_or_timeout_failure", | ||||||
|     "tag": false, |     "tag": false, | ||||||
|     "web_url": "https://example.com/foo/bar/-/jobs/6", |     "web_url": "https://example.com/foo/bar/-/jobs/6", | ||||||
|     "user": { |     "user": { | ||||||
|  | @ -271,6 +274,7 @@ Example of response | ||||||
|     "runner": null, |     "runner": null, | ||||||
|     "stage": "test", |     "stage": "test", | ||||||
|     "status": "failed", |     "status": "failed", | ||||||
|  |     "failure_reason": "script_failure", | ||||||
|     "tag": false, |     "tag": false, | ||||||
|     "web_url": "https://example.com/foo/bar/-/jobs/7", |     "web_url": "https://example.com/foo/bar/-/jobs/7", | ||||||
|     "user": { |     "user": { | ||||||
|  | @ -443,6 +447,7 @@ Example of response | ||||||
|   "runner": null, |   "runner": null, | ||||||
|   "stage": "test", |   "stage": "test", | ||||||
|   "status": "failed", |   "status": "failed", | ||||||
|  |   "failure_reason": "script_failure", | ||||||
|   "tag": false, |   "tag": false, | ||||||
|   "web_url": "https://example.com/foo/bar/-/jobs/8", |   "web_url": "https://example.com/foo/bar/-/jobs/8", | ||||||
|   "user": { |   "user": { | ||||||
|  |  | ||||||
|  | @ -145,6 +145,6 @@ PUT /projects/:id/external_status_checks/:check_id | ||||||
| | `external_url`         | string           | no       | URL of external status check resource          | | | `external_url`         | string           | no       | URL of external status check resource          | | ||||||
| | `protected_branch_ids` | `array<Integer>` | no       | IDs of protected branches to scope the rule by | | | `protected_branch_ids` | `array<Integer>` | no       | IDs of protected branches to scope the rule by | | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [External status checks](../user/project/merge_requests/status_checks.md). | - [External status checks](../user/project/merge_requests/status_checks.md). | ||||||
|  |  | ||||||
|  | @ -11,7 +11,7 @@ The GitLab API v3 was [removed](https://gitlab.com/gitlab-org/gitlab-foss/-/issu | ||||||
| 
 | 
 | ||||||
| For information about the current version of the GitLab API, read the [API documentation](index.md). | For information about the current version of the GitLab API, read the [API documentation](index.md). | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [GitLab v3 API documentation](https://gitlab.com/gitlab-org/gitlab-foss/-/blob/8-16-stable/doc/api/index.md) | - [GitLab v3 API documentation](https://gitlab.com/gitlab-org/gitlab-foss/-/blob/8-16-stable/doc/api/index.md) | ||||||
| - [Migration guide](https://gitlab.com/gitlab-org/gitlab-foss/-/blob/11-0-stable/doc/api/v3_to_v4.md) from | - [Migration guide](https://gitlab.com/gitlab-org/gitlab-foss/-/blob/11-0-stable/doc/api/v3_to_v4.md) from | ||||||
|  |  | ||||||
|  | @ -199,8 +199,10 @@ use `include:file`. You can use `include:file` in combination with `include:proj | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Global keyword. | **Keyword type**: Global keyword. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A full path, relative to the root directory (`/`). | **Possible inputs**: | ||||||
| The YAML file must have the extension `.yml` or `.yaml`. | 
 | ||||||
|  | - A full path, relative to the root directory (`/`). The YAML file must have the | ||||||
|  |   extension `.yml` or `.yaml`. | ||||||
| 
 | 
 | ||||||
| **Example of `include:file`**: | **Example of `include:file`**: | ||||||
| 
 | 
 | ||||||
|  | @ -255,10 +257,10 @@ Use `include:remote` with a full URL to include a file from a different location | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Global keyword. | **Keyword type**: Global keyword. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A public URL accessible by an HTTP/HTTPS `GET` request. | **Possible inputs**: | ||||||
| Authentication with the remote URL is not supported. |  | ||||||
| 
 | 
 | ||||||
| The YAML file must have the extension `.yml` or `.yaml`. | - A public URL accessible by an HTTP/HTTPS `GET` request. Authentication with the | ||||||
|  |   remote URL is not supported. The YAML file must have the extension `.yml` or `.yaml`. | ||||||
| 
 | 
 | ||||||
| **Example of `include:remote`**: | **Example of `include:remote`**: | ||||||
| 
 | 
 | ||||||
|  | @ -281,7 +283,9 @@ Use `include:template` to include [`.gitlab-ci.yml` templates](https://gitlab.co | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Global keyword. | **Keyword type**: Global keyword. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: [`.gitlab-ci.yml` templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates). | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - [`.gitlab-ci.yml` templates](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates). | ||||||
| 
 | 
 | ||||||
| **Example of `include:template`**: | **Example of `include:template`**: | ||||||
| 
 | 
 | ||||||
|  | @ -556,7 +560,9 @@ The default value for `allow_failure` is: | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: `true` or `false`. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - `true` or `false`. | ||||||
| 
 | 
 | ||||||
| **Example of `allow_failure`**: | **Example of `allow_failure`**: | ||||||
| 
 | 
 | ||||||
|  | @ -894,7 +900,9 @@ included templates in jobs. | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||||
| [`default` section](#default). | [`default` section](#default). | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: See list of available [artifacts reports types](artifacts_reports.md). | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - See list of available [artifacts reports types](artifacts_reports.md). | ||||||
| 
 | 
 | ||||||
| **Example of `artifacts:reports`**: | **Example of `artifacts:reports`**: | ||||||
| 
 | 
 | ||||||
|  | @ -1024,14 +1032,15 @@ Use the `cache:paths` keyword to choose which files or directories to cache. | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||||
| [`default` section](#default). | [`default` section](#default). | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: An array of paths relative to the project directory (`$CI_PROJECT_DIR`). | **Possible inputs**: | ||||||
| You can use wildcards that use [glob](https://en.wikipedia.org/wiki/Glob_(programming)) |  | ||||||
| patterns: |  | ||||||
| 
 | 
 | ||||||
| - In [GitLab Runner 13.0 and later](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2620), | - An array of paths relative to the project directory (`$CI_PROJECT_DIR`). | ||||||
| [`doublestar.Glob`](https://pkg.go.dev/github.com/bmatcuk/doublestar@v1.2.2?tab=doc#Match). |   You can use wildcards that use [glob](https://en.wikipedia.org/wiki/Glob_(programming)) | ||||||
| - In GitLab Runner 12.10 and earlier, |   patterns: | ||||||
| [`filepath.Match`](https://pkg.go.dev/path/filepath#Match). |   - In [GitLab Runner 13.0 and later](https://gitlab.com/gitlab-org/gitlab-runner/-/issues/2620), | ||||||
|  |   [`doublestar.Glob`](https://pkg.go.dev/github.com/bmatcuk/doublestar@v1.2.2?tab=doc#Match). | ||||||
|  |   - In GitLab Runner 12.10 and earlier, | ||||||
|  |   [`filepath.Match`](https://pkg.go.dev/path/filepath#Match). | ||||||
| 
 | 
 | ||||||
| **Example of `cache:paths`**: | **Example of `cache:paths`**: | ||||||
| 
 | 
 | ||||||
|  | @ -1114,7 +1123,9 @@ which speeds up subsequent pipeline runs. | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||||
| [`default` section](#default). | [`default` section](#default). | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: An array of one or two file paths. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - An array of one or two file paths. | ||||||
| 
 | 
 | ||||||
| **Example of `cache:key:files`**: | **Example of `cache:key:files`**: | ||||||
| 
 | 
 | ||||||
|  | @ -1190,7 +1201,9 @@ Use `untracked: true` to cache all files that are untracked in your Git reposito | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||||
| [`default` section](#default). | [`default` section](#default). | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: `true` or `false` (default). | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - `true` or `false` (default). | ||||||
| 
 | 
 | ||||||
| **Example of `cache:untracked`**: | **Example of `cache:untracked`**: | ||||||
| 
 | 
 | ||||||
|  | @ -1303,7 +1316,9 @@ line in the job output matches the regular expression. | ||||||
| To extract the code coverage value in the matching line, GitLab uses this | To extract the code coverage value in the matching line, GitLab uses this | ||||||
| regular expression: `\d+(\.\d+)?`. | regular expression: `\d+(\.\d+)?`. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A regular expression. Must start and end with `/`. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - A regular expression. Must start and end with `/`. | ||||||
| 
 | 
 | ||||||
| **Example of `coverage`**: | **Example of `coverage`**: | ||||||
| 
 | 
 | ||||||
|  | @ -1811,7 +1826,9 @@ where each shell token is a separate string in the array. | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||||
| [`default` section](#default). | [`default` section](#default). | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A string. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - A string. | ||||||
| 
 | 
 | ||||||
| **Example of `image:entrypoint`**: | **Example of `image:entrypoint`**: | ||||||
| 
 | 
 | ||||||
|  | @ -1918,7 +1935,9 @@ You can't cancel subsequent jobs after a job with `interruptible: false` starts. | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||||
| [`default` section](#default). | [`default` section](#default). | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: `true` or `false` (default). | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - `true` or `false` (default). | ||||||
| 
 | 
 | ||||||
| **Example of `interruptible`**: | **Example of `interruptible`**: | ||||||
| 
 | 
 | ||||||
|  | @ -2411,7 +2430,9 @@ to a pipeline, based on the status of [CI/CD variables](../variables/index.md). | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: An array of [CI/CD variable expressions](../jobs/job_control.md#cicd-variable-expressions). | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - An array of [CI/CD variable expressions](../jobs/job_control.md#cicd-variable-expressions). | ||||||
| 
 | 
 | ||||||
| **Example of `only:variables`**: | **Example of `only:variables`**: | ||||||
| 
 | 
 | ||||||
|  | @ -2490,7 +2511,9 @@ when the Kubernetes service is active in the project. | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job-specific. You can use it only as part of a job. | **Keyword type**: Job-specific. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: The `kubernetes` strategy accepts only the `active` keyword. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - The `kubernetes` strategy accepts only the `active` keyword. | ||||||
| 
 | 
 | ||||||
| **Example of `only:kubernetes`**: | **Example of `only:kubernetes`**: | ||||||
| 
 | 
 | ||||||
|  | @ -2546,7 +2569,9 @@ Parallel jobs are named sequentially from `job_name 1/N` to `job_name N/N`. | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A numeric value from `2` to `50`. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - A numeric value from `2` to `50`. | ||||||
| 
 | 
 | ||||||
| **Example of `parallel`**: | **Example of `parallel`**: | ||||||
| 
 | 
 | ||||||
|  | @ -2579,7 +2604,9 @@ Multiple runners must exist, or a single runner must be configured to run multip | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A numeric value from `2` to `50`. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - A numeric value from `2` to `50`. | ||||||
| 
 | 
 | ||||||
| **Example of `parallel:matrix`**: | **Example of `parallel:matrix`**: | ||||||
| 
 | 
 | ||||||
|  | @ -2699,7 +2726,9 @@ New tags use the SHA associated with the pipeline. | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A tag name. Can use [CI/CD variables](../variables/index.md). | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - A tag name. Can use [CI/CD variables](../variables/index.md). | ||||||
| 
 | 
 | ||||||
| **Example of `release:tag_name`**: | **Example of `release:tag_name`**: | ||||||
| 
 | 
 | ||||||
|  | @ -2738,7 +2767,9 @@ The release name. If omitted, it is populated with the value of `release: tag_na | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A text string. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - A text string. | ||||||
| 
 | 
 | ||||||
| **Example of `release:name`**: | **Example of `release:name`**: | ||||||
| 
 | 
 | ||||||
|  | @ -2843,8 +2874,10 @@ can be deployed to, but only one deployment can occur per device at any given ti | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: Only letters, digits, `-`, `_`, `/`, `$`, `{`, `}`, `.`, and spaces. | **Possible inputs**: | ||||||
| It can't start or end with `/`. | 
 | ||||||
|  | - Only letters, digits, `-`, `_`, `/`, `$`, `{`, `}`, `.`, and spaces. | ||||||
|  |   It can't start or end with `/`. | ||||||
| 
 | 
 | ||||||
| **Example of `resource_group`**: | **Example of `resource_group`**: | ||||||
| 
 | 
 | ||||||
|  | @ -2875,7 +2908,9 @@ to select which failures to retry on. | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||||
| [`default` section](#default). | [`default` section](#default). | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: `0` (default), `1`, or `2`. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - `0` (default), `1`, or `2`. | ||||||
| 
 | 
 | ||||||
| **Example of `retry`**: | **Example of `retry`**: | ||||||
| 
 | 
 | ||||||
|  | @ -2894,7 +2929,9 @@ Use `retry:when` with `retry:max` to retry jobs for only specific failure cases. | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job or in the | **Keyword type**: Job keyword. You can use it only as part of a job or in the | ||||||
| [`default` section](#default). | [`default` section](#default). | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A single failure type, or an array of one or more failure types: | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - A single failure type, or an array of one or more failure types: | ||||||
| 
 | 
 | ||||||
| <!-- | <!-- | ||||||
|   If you change any of the values below, make sure to update the `RETRY_WHEN_IN_DOCUMENTATION` |   If you change any of the values below, make sure to update the `RETRY_WHEN_IN_DOCUMENTATION` | ||||||
|  | @ -3002,7 +3039,9 @@ or [custom CI/CD variables](../variables/index.md#custom-cicd-variables). | ||||||
| **Keyword type**: Job-specific and pipeline-specific. You can use it as part of a job | **Keyword type**: Job-specific and pipeline-specific. You can use it as part of a job | ||||||
| to configure the job behavior, or with [`workflow`](#workflow) to configure the pipeline behavior. | to configure the job behavior, or with [`workflow`](#workflow) to configure the pipeline behavior. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A [CI/CD variable expression](../jobs/job_control.md#cicd-variable-expressions). | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - A [CI/CD variable expression](../jobs/job_control.md#cicd-variable-expressions). | ||||||
| 
 | 
 | ||||||
| **Example of `rules:if`**: | **Example of `rules:if`**: | ||||||
| 
 | 
 | ||||||
|  | @ -3048,8 +3087,9 @@ branch or merge request pipelines. | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: An array of file paths. In GitLab 13.6 and later, | **Possible inputs**: | ||||||
| [file paths can include variables](../jobs/job_control.md#variables-in-ruleschanges). | 
 | ||||||
|  | - An array of file paths. In GitLab 13.6 and later, [file paths can include variables](../jobs/job_control.md#variables-in-ruleschanges). | ||||||
| 
 | 
 | ||||||
| **Example of `rules:changes`**: | **Example of `rules:changes`**: | ||||||
| 
 | 
 | ||||||
|  | @ -3083,8 +3123,10 @@ Use `exists` to run a job when certain files exist in the repository. | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: An array of file paths. Paths are relative to the project directory (`$CI_PROJECT_DIR`) | **Possible inputs**: | ||||||
| and can't directly link outside it. File paths can use glob patterns. | 
 | ||||||
|  | - An array of file paths. Paths are relative to the project directory (`$CI_PROJECT_DIR`) | ||||||
|  |   and can't directly link outside it. File paths can use glob patterns. | ||||||
| 
 | 
 | ||||||
| **Example of `rules:exists`**: | **Example of `rules:exists`**: | ||||||
| 
 | 
 | ||||||
|  | @ -3122,7 +3164,9 @@ job to run before continuing. | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: `true` or `false`. Defaults to `false` if not defined. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - `true` or `false`. Defaults to `false` if not defined. | ||||||
| 
 | 
 | ||||||
| **Example of `rules:allow_failure`**: | **Example of `rules:allow_failure`**: | ||||||
| 
 | 
 | ||||||
|  | @ -3151,7 +3195,9 @@ Use [`variables`](#variables) in `rules` to define variables for specific condit | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job-specific. You can use it only as part of a job. | **Keyword type**: Job-specific. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A hash of variables in the format `VARIABLE-NAME: value`. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - A hash of variables in the format `VARIABLE-NAME: value`. | ||||||
| 
 | 
 | ||||||
| **Example of `rules:variables`**: | **Example of `rules:variables`**: | ||||||
| 
 | 
 | ||||||
|  | @ -3286,7 +3332,9 @@ the secret value directly in the variable. | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Job keyword. You can use it only as part of a job. | **Keyword type**: Job keyword. You can use it only as part of a job. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: `true` (default) or `false`. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - `true` (default) or `false`. | ||||||
| 
 | 
 | ||||||
| **Example of `secrets:file`**: | **Example of `secrets:file`**: | ||||||
| 
 | 
 | ||||||
|  | @ -3682,7 +3730,9 @@ Must be used with `value`, for the variable value. | ||||||
| 
 | 
 | ||||||
| **Keyword type**: Global keyword. You cannot set job-level variables to be pre-filled when you run a pipeline manually. | **Keyword type**: Global keyword. You cannot set job-level variables to be pre-filled when you run a pipeline manually. | ||||||
| 
 | 
 | ||||||
| **Possible inputs**: A string. | **Possible inputs**: | ||||||
|  | 
 | ||||||
|  | - A string. | ||||||
| 
 | 
 | ||||||
| **Example of `variables:description`**: | **Example of `variables:description`**: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -134,7 +134,7 @@ After configuring GitLab for the two databases, create the new CI/CD database: | ||||||
|    and run any pending migrations: |    and run any pending migrations: | ||||||
| 
 | 
 | ||||||
|     ```shell |     ```shell | ||||||
|     bundle exec rails rails db:create db:schema:load:ci db:migrate |     bundle exec rails db:create db:schema:load:ci db:migrate | ||||||
|     ``` |     ``` | ||||||
| 
 | 
 | ||||||
| 1. Restart GDK: | 1. Restart GDK: | ||||||
|  |  | ||||||
|  | @ -220,6 +220,20 @@ The `* as-if-jh` jobs are run in addition to the regular EE-context jobs. The `j | ||||||
| The intent is to ensure that a change doesn't introduce a failure after the `gitlab-org/gitlab` project is synced to | The intent is to ensure that a change doesn't introduce a failure after the `gitlab-org/gitlab` project is synced to | ||||||
| the `gitlab-jh/gitlab` project. | the `gitlab-jh/gitlab` project. | ||||||
| 
 | 
 | ||||||
|  | ## `undercover` RSpec test | ||||||
|  | 
 | ||||||
|  | > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/74859) in GitLab 14.6. | ||||||
|  | 
 | ||||||
|  | The `rspec:undercoverage` job runs [`undercover`](https://rubygems.org/gems/undercover) | ||||||
|  | to detect, and fail if any changes introduced in the merge request has zero coverage. | ||||||
|  | 
 | ||||||
|  | The `rsepc:undercoverage` job obtains coverage data from the `rspec:coverage` | ||||||
|  | job. | ||||||
|  | 
 | ||||||
|  | In the event of an emergency, or false positive from this job, add the | ||||||
|  | `pipeline:skip-undercoverage` label to the merge request to allow this job to | ||||||
|  | fail. | ||||||
|  | 
 | ||||||
| ## PostgreSQL versions testing | ## PostgreSQL versions testing | ||||||
| 
 | 
 | ||||||
| Our test suite runs against PG12 as GitLab.com runs on PG12 and | Our test suite runs against PG12 as GitLab.com runs on PG12 and | ||||||
|  |  | ||||||
|  | @ -46,6 +46,6 @@ project, group, or instance level: | ||||||
| When the integration sends data, you can view it in the [CI Visibility](https://app.datadoghq.com/ci) | When the integration sends data, you can view it in the [CI Visibility](https://app.datadoghq.com/ci) | ||||||
| section of your Datadog account. | section of your Datadog account. | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [Datadog's CI Visibility](https://docs.datadoghq.com/continuous_integration/) documentation. | - [Datadog's CI Visibility](https://docs.datadoghq.com/continuous_integration/) documentation. | ||||||
|  |  | ||||||
|  | @ -74,7 +74,7 @@ into a different branch (`stable`): | ||||||
|    git cherry-pick <SHA> |    git cherry-pick <SHA> | ||||||
|    ``` |    ``` | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - Cherry-pick commits with [the Commits API](../../api/commits.md#cherry-pick-a-commit) | - Cherry-pick commits with [the Commits API](../../api/commits.md#cherry-pick-a-commit) | ||||||
| - Git documentation [for cherry-picks](https://git-scm.com/docs/git-cherry-pick) | - Git documentation [for cherry-picks](https://git-scm.com/docs/git-cherry-pick) | ||||||
|  |  | ||||||
|  | @ -88,7 +88,7 @@ sees in the project's search results respectively. | ||||||
| |:---------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------| | |:---------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------| | ||||||
| |  |  | | |  |  | | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [Merge requests for confidential issues](../merge_requests/confidential.md) | - [Merge requests for confidential issues](../merge_requests/confidential.md) | ||||||
| - [Make an epic confidential](../../group/epics/manage_epics.md#make-an-epic-confidential) | - [Make an epic confidential](../../group/epics/manage_epics.md#make-an-epic-confidential) | ||||||
|  |  | ||||||
|  | @ -105,7 +105,7 @@ Without the approvals, the work cannot merge. Required approvals enable multiple | ||||||
| - [Require approval from a security team](../../../application_security/index.md#security-approvals-in-merge-requests) | - [Require approval from a security team](../../../application_security/index.md#security-approvals-in-merge-requests) | ||||||
|   before merging code that could introduce a vulnerability. **(ULTIMATE)** |   before merging code that could introduce a vulnerability. **(ULTIMATE)** | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [Merge request approvals API](../../../../api/merge_request_approvals.md) | - [Merge request approvals API](../../../../api/merge_request_approvals.md) | ||||||
| - [Instance-level approval rules](../../../admin_area/merge_requests_approvals.md) for self-managed installations | - [Instance-level approval rules](../../../admin_area/merge_requests_approvals.md) for self-managed installations | ||||||
|  |  | ||||||
|  | @ -157,7 +157,7 @@ You can also enforce merge request approval settings: | ||||||
| If the settings are inherited by a group or project, they cannot be changed in the group or project | If the settings are inherited by a group or project, they cannot be changed in the group or project | ||||||
| that inherited them. | that inherited them. | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [Instance-level merge request approval settings](../../../admin_area/merge_requests_approvals.md) | - [Instance-level merge request approval settings](../../../admin_area/merge_requests_approvals.md) | ||||||
| - [Compliance report](../../../compliance/compliance_report/index.md) | - [Compliance report](../../../compliance/compliance_report/index.md) | ||||||
|  |  | ||||||
|  | @ -79,7 +79,7 @@ merge request is from a fork: | ||||||
| 1. (Optional) Select **Start a new merge request** if you're ready to create a merge request. | 1. (Optional) Select **Start a new merge request** if you're ready to create a merge request. | ||||||
| 1. Click **Cherry-pick**. | 1. Click **Cherry-pick**. | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - The [Commits API](../../../api/commits.md) enables you to add custom messages | - The [Commits API](../../../api/commits.md) enables you to add custom messages | ||||||
|   to changes you cherry-pick through the API. |   to changes you cherry-pick through the API. | ||||||
|  |  | ||||||
|  | @ -70,7 +70,7 @@ Open a merge request | ||||||
| - You are satisfied the problem is resolved in your private fork. | - You are satisfied the problem is resolved in your private fork. | ||||||
| - You are ready to make the confidential commits public. | - You are ready to make the confidential commits public. | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [Confidential issues](../issues/confidential_issues.md) | - [Confidential issues](../issues/confidential_issues.md) | ||||||
| - [Make an epic confidential](../../group/epics/manage_epics.md#make-an-epic-confidential) | - [Make an epic confidential](../../group/epics/manage_epics.md#make-an-epic-confidential) | ||||||
|  |  | ||||||
|  | @ -144,6 +144,6 @@ to your branch to address your reviewers' requests. | ||||||
| WARNING: | WARNING: | ||||||
| Suggestions applied from multiple authors creates a commit authored by the user applying the suggestions. | Suggestions applied from multiple authors creates a commit authored by the user applying the suggestions. | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [Suggestions API](../../../../api/suggestions.md) | - [Suggestions API](../../../../api/suggestions.md) | ||||||
|  |  | ||||||
|  | @ -181,6 +181,6 @@ You should: | ||||||
| - Check the [GitLab status page](https://status.gitlab.com/) if the problem persists, | - Check the [GitLab status page](https://status.gitlab.com/) if the problem persists, | ||||||
|   to see if there is a wider outage. |   to see if there is a wider outage. | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [External status checks API](../../../api/status_checks.md) | - [External status checks API](../../../api/status_checks.md) | ||||||
|  |  | ||||||
|  | @ -248,9 +248,19 @@ When you [rename a user](../../profile/index.md#change-your-username), | ||||||
| - The redirects are available as long as the original path is not claimed by | - The redirects are available as long as the original path is not claimed by | ||||||
|   another group, user, or project. |   another group, user, or project. | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [GitLab Workflow VS Code extension](vscode.md) | - [GitLab Workflow VS Code extension](vscode.md). | ||||||
|  | - To lock files and prevent change conflicts, use [file locking](../file_lock.md). | ||||||
|  | - [Repository API](../../../api/repositories.md). | ||||||
|  | - [Find files](file_finder.md) in a repository. | ||||||
|  | - [Branches](branches/index.md). | ||||||
|  | - [File templates](web_editor.md#template-dropdowns). | ||||||
|  | - [Create a directory](web_editor.md#create-a-directory). | ||||||
|  | - [Start a merge request](web_editor.md#tips). | ||||||
|  | - [Find file history](git_history.md). | ||||||
|  | - [Identify changes by line (Git blame)](git_blame.md). | ||||||
|  | - [Use Jupyter notebooks with GitLab](jupyter_notebooks/index.md). | ||||||
| 
 | 
 | ||||||
| ## Troubleshooting | ## Troubleshooting | ||||||
| 
 | 
 | ||||||
|  | @ -287,16 +297,3 @@ The same approach should also allow misidentified file types to be fixed. | ||||||
|    ``` |    ``` | ||||||
| 
 | 
 | ||||||
|   `*.txt` files have an entry in the heuristics file. This example prevents parsing of these files. |   `*.txt` files have an entry in the heuristics file. This example prevents parsing of these files. | ||||||
| 
 |  | ||||||
| ## Related topics |  | ||||||
| 
 |  | ||||||
| - To lock files and prevent change conflicts, use [file locking](../file_lock.md). |  | ||||||
| - [Repository API](../../../api/repositories.md). |  | ||||||
| - [Find files](file_finder.md) in a repository. |  | ||||||
| - [Branches](branches/index.md). |  | ||||||
| - [File templates](web_editor.md#template-dropdowns). |  | ||||||
| - [Create a directory](web_editor.md#create-a-directory). |  | ||||||
| - [Start a merge request](web_editor.md#tips). |  | ||||||
| - [Find file history](git_history.md). |  | ||||||
| - [Identify changes by line (Git blame)](git_blame.md). |  | ||||||
| - [Use Jupyter notebooks with GitLab](jupyter_notebooks/index.md). |  | ||||||
|  |  | ||||||
|  | @ -123,7 +123,7 @@ To do so: | ||||||
| 
 | 
 | ||||||
| With this option enabled, `75h` is displayed instead of `1w 4d 3h`. | With this option enabled, `75h` is displayed instead of `1w 4d 3h`. | ||||||
| 
 | 
 | ||||||
| ## Related links | ## Related topics | ||||||
| 
 | 
 | ||||||
| - [Time tracking solutions page](https://about.gitlab.com/solutions/time-tracking/) | - [Time tracking solutions page](https://about.gitlab.com/solutions/time-tracking/) | ||||||
| - Time tracking GraphQL references: | - Time tracking GraphQL references: | ||||||
|  |  | ||||||
|  | @ -13,6 +13,7 @@ module API | ||||||
|         expose :user, with: ::API::Entities::User |         expose :user, with: ::API::Entities::User | ||||||
|         expose :commit, with: ::API::Entities::Commit |         expose :commit, with: ::API::Entities::Commit | ||||||
|         expose :pipeline, with: ::API::Entities::Ci::PipelineBasic |         expose :pipeline, with: ::API::Entities::Ci::PipelineBasic | ||||||
|  |         expose :failure_reason, if: -> (job) { job.failed? } | ||||||
| 
 | 
 | ||||||
|         expose :web_url do |job, _options| |         expose :web_url do |job, _options| | ||||||
|           Gitlab::Routing.url_helpers.project_job_url(job.project, job) |           Gitlab::Routing.url_helpers.project_job_url(job.project, job) | ||||||
|  |  | ||||||
|  | @ -4,8 +4,6 @@ module Gitlab | ||||||
|     module Partitioning |     module Partitioning | ||||||
|       class DetachedPartitionDropper |       class DetachedPartitionDropper | ||||||
|         def perform |         def perform | ||||||
|           return unless Feature.enabled?(:drop_detached_partitions, default_enabled: :yaml) |  | ||||||
| 
 |  | ||||||
|           Gitlab::AppLogger.info(message: "Checking for previously detached partitions to drop") |           Gitlab::AppLogger.info(message: "Checking for previously detached partitions to drop") | ||||||
| 
 | 
 | ||||||
|           Postgresql::DetachedPartition.ready_to_drop.find_each do |detached_partition| |           Postgresql::DetachedPartition.ready_to_drop.find_each do |detached_partition| | ||||||
|  |  | ||||||
|  | @ -25,11 +25,9 @@ module Gitlab | ||||||
|             partitions_to_create = missing_partitions |             partitions_to_create = missing_partitions | ||||||
|             create(partitions_to_create) unless partitions_to_create.empty? |             create(partitions_to_create) unless partitions_to_create.empty? | ||||||
| 
 | 
 | ||||||
|             if Feature.enabled?(:partition_pruning, default_enabled: :yaml) |  | ||||||
|             partitions_to_detach = extra_partitions |             partitions_to_detach = extra_partitions | ||||||
|             detach(partitions_to_detach) unless partitions_to_detach.empty? |             detach(partitions_to_detach) unless partitions_to_detach.empty? | ||||||
|           end |           end | ||||||
|           end |  | ||||||
|         rescue StandardError => e |         rescue StandardError => e | ||||||
|           Gitlab::AppLogger.error(message: "Failed to create / detach partition(s)", |           Gitlab::AppLogger.error(message: "Failed to create / detach partition(s)", | ||||||
|                                   table_name: model.table_name, |                                   table_name: model.table_name, | ||||||
|  |  | ||||||
|  | @ -19,7 +19,7 @@ module QA | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def image |       def image | ||||||
|         @image || 'gitlab/gitlab-runner:alpine' |         @image || 'registry.gitlab.com/gitlab-org/gitlab-runner:alpine' | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       def executor |       def executor | ||||||
|  |  | ||||||
|  | @ -0,0 +1,3 @@ | ||||||
|  | #!/usr/bin/env bash | ||||||
|  | 
 | ||||||
|  | bundle exec undercover -c "${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$(git merge-base origin/master HEAD)}" | ||||||
|  | @ -429,6 +429,38 @@ | ||||||
|     </figcaption> |     </figcaption> | ||||||
|     </figure> |     </figure> | ||||||
| 
 | 
 | ||||||
|  | - name: footnotes | ||||||
|  |   substitutions: | ||||||
|  |     # NOTE: We don't care about verifying specific attribute values here, that should be the | ||||||
|  |     #       responsibility of unit tests. These tests are about the structure of the HTML. | ||||||
|  |     fn_href_substitution: | ||||||
|  |       - regex: '(href)(=")(.+?)(")' | ||||||
|  |         replacement: '\1\2REF\4' | ||||||
|  |     footnote_id_substitution: | ||||||
|  |       - regex: '(id)(=")(.+?)(")' | ||||||
|  |         replacement: '\1\2ID\4' | ||||||
|  | 
 | ||||||
|  |   pending: | ||||||
|  |     backend: https://gitlab.com/gitlab-org/gitlab/-/issues/346591 | ||||||
|  |   markdown: |- | ||||||
|  |     A footnote reference tag looks like this: [^1] | ||||||
|  | 
 | ||||||
|  |     This reference tag is a mix of letters and numbers. [^2] | ||||||
|  | 
 | ||||||
|  |     [^1]: This is the text inside a footnote. | ||||||
|  |     [^2]: This is another footnote. | ||||||
|  |   html: |- | ||||||
|  |     <p data-sourcepos="1:1-1:46" dir="auto">A footnote reference tag looks like this: <sup class="footnote-ref"><a href="#fn-1-2717" id="fnref-1-2717" data-footnote-ref="">1</a></sup></p> | ||||||
|  |     <p data-sourcepos="3:1-3:56" dir="auto">This reference tag is a mix of letters and numbers. <sup class="footnote-ref"><a href="#fn-2-2717" id="fnref-2-2717" data-footnote-ref="">2</a></sup></p> | ||||||
|  |     <section class="footnotes" data-footnotes><ol> | ||||||
|  |     <li id="fn-1-2717"> | ||||||
|  |     <p data-sourcepos="5:7-5:41">This is the text inside a footnote. <a href="#fnref-1-2717" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> | ||||||
|  |     </li> | ||||||
|  |     <li id="fn-2-2717"> | ||||||
|  |     <p data-sourcepos="6:7-6:31">This is another footnote. <a href="#fnref-2-2717" aria-label="Back to content" class="footnote-backref" data-footnote-backref=""><gl-emoji title="leftwards arrow with hook" data-name="leftwards_arrow_with_hook" data-unicode-version="1.1">↩</gl-emoji></a></p> | ||||||
|  |     </li> | ||||||
|  |     </ol></section> | ||||||
|  | 
 | ||||||
| - name: frontmatter_json | - name: frontmatter_json | ||||||
|   markdown: |- |   markdown: |- | ||||||
|     ;;; |     ;;; | ||||||
|  |  | ||||||
|  | @ -11,6 +11,9 @@ import Division from '~/content_editor/extensions/division'; | ||||||
| import Emoji from '~/content_editor/extensions/emoji'; | import Emoji from '~/content_editor/extensions/emoji'; | ||||||
| import Figure from '~/content_editor/extensions/figure'; | import Figure from '~/content_editor/extensions/figure'; | ||||||
| import FigureCaption from '~/content_editor/extensions/figure_caption'; | import FigureCaption from '~/content_editor/extensions/figure_caption'; | ||||||
|  | import FootnoteDefinition from '~/content_editor/extensions/footnote_definition'; | ||||||
|  | import FootnoteReference from '~/content_editor/extensions/footnote_reference'; | ||||||
|  | import FootnotesSection from '~/content_editor/extensions/footnotes_section'; | ||||||
| import HardBreak from '~/content_editor/extensions/hard_break'; | import HardBreak from '~/content_editor/extensions/hard_break'; | ||||||
| import Heading from '~/content_editor/extensions/heading'; | import Heading from '~/content_editor/extensions/heading'; | ||||||
| import HorizontalRule from '~/content_editor/extensions/horizontal_rule'; | import HorizontalRule from '~/content_editor/extensions/horizontal_rule'; | ||||||
|  | @ -46,6 +49,9 @@ const tiptapEditor = createTestEditor({ | ||||||
|     DetailsContent, |     DetailsContent, | ||||||
|     Division, |     Division, | ||||||
|     Emoji, |     Emoji, | ||||||
|  |     FootnoteDefinition, | ||||||
|  |     FootnoteReference, | ||||||
|  |     FootnotesSection, | ||||||
|     Figure, |     Figure, | ||||||
|     FigureCaption, |     FigureCaption, | ||||||
|     HardBreak, |     HardBreak, | ||||||
|  | @ -81,6 +87,9 @@ const { | ||||||
|     descriptionItem, |     descriptionItem, | ||||||
|     descriptionList, |     descriptionList, | ||||||
|     emoji, |     emoji, | ||||||
|  |     footnoteDefinition, | ||||||
|  |     footnoteReference, | ||||||
|  |     footnotesSection, | ||||||
|     figure, |     figure, | ||||||
|     figureCaption, |     figureCaption, | ||||||
|     heading, |     heading, | ||||||
|  | @ -117,6 +126,9 @@ const { | ||||||
|     emoji: { markType: Emoji.name }, |     emoji: { markType: Emoji.name }, | ||||||
|     figure: { nodeType: Figure.name }, |     figure: { nodeType: Figure.name }, | ||||||
|     figureCaption: { nodeType: FigureCaption.name }, |     figureCaption: { nodeType: FigureCaption.name }, | ||||||
|  |     footnoteDefinition: { nodeType: FootnoteDefinition.name }, | ||||||
|  |     footnoteReference: { nodeType: FootnoteReference.name }, | ||||||
|  |     footnotesSection: { nodeType: FootnotesSection.name }, | ||||||
|     hardBreak: { nodeType: HardBreak.name }, |     hardBreak: { nodeType: HardBreak.name }, | ||||||
|     heading: { nodeType: Heading.name }, |     heading: { nodeType: Heading.name }, | ||||||
|     horizontalRule: { nodeType: HorizontalRule.name }, |     horizontalRule: { nodeType: HorizontalRule.name }, | ||||||
|  | @ -1105,4 +1117,22 @@ there | ||||||
|       `.trim(),
 |       `.trim(),
 | ||||||
|     ); |     ); | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   it('correctly serializes footnotes', () => { | ||||||
|  |     expect( | ||||||
|  |       serialize( | ||||||
|  |         paragraph( | ||||||
|  |           'Oranges are orange ', | ||||||
|  |           footnoteReference({ footnoteId: '1', footnoteNumber: '1' }), | ||||||
|  |         ), | ||||||
|  |         footnotesSection(footnoteDefinition(paragraph('Oranges are fruits'))), | ||||||
|  |       ), | ||||||
|  |     ).toBe( | ||||||
|  |       ` | ||||||
|  | Oranges are orange [^1] | ||||||
|  | 
 | ||||||
|  | [^1]: Oranges are fruits | ||||||
|  |       `.trim(),
 | ||||||
|  |     ); | ||||||
|  |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -0,0 +1,220 @@ | ||||||
|  | import { merge } from 'lodash'; | ||||||
|  | import { GlFormInputGroup } from '@gitlab/ui'; | ||||||
|  | 
 | ||||||
|  | import InputCopyToggleVisibility from '~/vue_shared/components/form/input_copy_toggle_visibility.vue'; | ||||||
|  | import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; | ||||||
|  | import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; | ||||||
|  | 
 | ||||||
|  | import { mountExtended } from 'helpers/vue_test_utils_helper'; | ||||||
|  | 
 | ||||||
|  | describe('InputCopyToggleVisibility', () => { | ||||||
|  |   let wrapper; | ||||||
|  | 
 | ||||||
|  |   afterEach(() => { | ||||||
|  |     wrapper.destroy(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   const valueProp = 'hR8x1fuJbzwu5uFKLf9e'; | ||||||
|  | 
 | ||||||
|  |   const createComponent = (options = {}) => { | ||||||
|  |     wrapper = mountExtended( | ||||||
|  |       InputCopyToggleVisibility, | ||||||
|  |       merge({}, options, { | ||||||
|  |         directives: { | ||||||
|  |           GlTooltip: createMockDirective(), | ||||||
|  |         }, | ||||||
|  |       }), | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const findFormInputGroup = () => wrapper.findComponent(GlFormInputGroup); | ||||||
|  |   const findFormInput = () => findFormInputGroup().find('input'); | ||||||
|  |   const findRevealButton = () => | ||||||
|  |     wrapper.findByRole('button', { | ||||||
|  |       name: InputCopyToggleVisibility.i18n.toggleVisibilityLabelReveal, | ||||||
|  |     }); | ||||||
|  |   const findHideButton = () => | ||||||
|  |     wrapper.findByRole('button', { | ||||||
|  |       name: InputCopyToggleVisibility.i18n.toggleVisibilityLabelHide, | ||||||
|  |     }); | ||||||
|  |   const findCopyButton = () => wrapper.findComponent(ClipboardButton); | ||||||
|  |   const createCopyEvent = () => { | ||||||
|  |     const event = new Event('copy', { cancelable: true }); | ||||||
|  |     Object.assign(event, { preventDefault: jest.fn(), clipboardData: { setData: jest.fn() } }); | ||||||
|  | 
 | ||||||
|  |     return event; | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const itDoesNotModifyCopyEvent = () => { | ||||||
|  |     it('does not modify copy event', () => { | ||||||
|  |       const event = createCopyEvent(); | ||||||
|  | 
 | ||||||
|  |       findFormInput().element.dispatchEvent(event); | ||||||
|  | 
 | ||||||
|  |       expect(event.clipboardData.setData).not.toHaveBeenCalled(); | ||||||
|  |       expect(event.preventDefault).not.toHaveBeenCalled(); | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   describe('when `value` prop is passed', () => { | ||||||
|  |     beforeEach(() => { | ||||||
|  |       createComponent({ | ||||||
|  |         propsData: { | ||||||
|  |           value: valueProp, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('displays value as hidden', () => { | ||||||
|  |       expect(findFormInputGroup().props('value')).toBe('********************'); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('saves actual value to clipboard when manually copied', () => { | ||||||
|  |       const event = createCopyEvent(); | ||||||
|  |       findFormInput().element.dispatchEvent(event); | ||||||
|  | 
 | ||||||
|  |       expect(event.clipboardData.setData).toHaveBeenCalledWith('text/plain', valueProp); | ||||||
|  |       expect(event.preventDefault).toHaveBeenCalled(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     describe('visibility toggle button', () => { | ||||||
|  |       it('renders a reveal button', () => { | ||||||
|  |         const revealButton = findRevealButton(); | ||||||
|  | 
 | ||||||
|  |         expect(revealButton.exists()).toBe(true); | ||||||
|  | 
 | ||||||
|  |         const tooltip = getBinding(revealButton.element, 'gl-tooltip'); | ||||||
|  | 
 | ||||||
|  |         expect(tooltip.value).toBe(InputCopyToggleVisibility.i18n.toggleVisibilityLabelReveal); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       describe('when clicked', () => { | ||||||
|  |         beforeEach(async () => { | ||||||
|  |           await findRevealButton().trigger('click'); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         it('displays value', () => { | ||||||
|  |           expect(findFormInputGroup().props('value')).toBe(valueProp); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         it('renders a hide button', () => { | ||||||
|  |           const hideButton = findHideButton(); | ||||||
|  | 
 | ||||||
|  |           expect(hideButton.exists()).toBe(true); | ||||||
|  | 
 | ||||||
|  |           const tooltip = getBinding(hideButton.element, 'gl-tooltip'); | ||||||
|  | 
 | ||||||
|  |           expect(tooltip.value).toBe(InputCopyToggleVisibility.i18n.toggleVisibilityLabelHide); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         it('emits `visibility-change` event', () => { | ||||||
|  |           expect(wrapper.emitted('visibility-change')[0]).toEqual([true]); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     describe('copy button', () => { | ||||||
|  |       it('renders button with correct props passed', () => { | ||||||
|  |         expect(findCopyButton().props()).toMatchObject({ | ||||||
|  |           text: valueProp, | ||||||
|  |           title: 'Copy', | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       describe('when clicked', () => { | ||||||
|  |         beforeEach(async () => { | ||||||
|  |           await findCopyButton().trigger('click'); | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         it('emits `copy` event', () => { | ||||||
|  |           expect(wrapper.emitted('copy')[0]).toEqual([]); | ||||||
|  |         }); | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('when `value` prop is not passed', () => { | ||||||
|  |     beforeEach(() => { | ||||||
|  |       createComponent(); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('displays value as hidden with 20 asterisks', () => { | ||||||
|  |       expect(findFormInputGroup().props('value')).toBe('********************'); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('when `initialVisibility` prop is `true`', () => { | ||||||
|  |     beforeEach(() => { | ||||||
|  |       createComponent({ | ||||||
|  |         propsData: { | ||||||
|  |           value: valueProp, | ||||||
|  |           initialVisibility: true, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('displays value', () => { | ||||||
|  |       expect(findFormInputGroup().props('value')).toBe(valueProp); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     itDoesNotModifyCopyEvent(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('when `showToggleVisibilityButton` is `false`', () => { | ||||||
|  |     beforeEach(() => { | ||||||
|  |       createComponent({ | ||||||
|  |         propsData: { | ||||||
|  |           value: valueProp, | ||||||
|  |           showToggleVisibilityButton: false, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('does not render visibility toggle button', () => { | ||||||
|  |       expect(findRevealButton().exists()).toBe(false); | ||||||
|  |       expect(findHideButton().exists()).toBe(false); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('displays value', () => { | ||||||
|  |       expect(findFormInputGroup().props('value')).toBe(valueProp); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     itDoesNotModifyCopyEvent(); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   describe('when `showCopyButton` is `false`', () => { | ||||||
|  |     beforeEach(() => { | ||||||
|  |       createComponent({ | ||||||
|  |         propsData: { | ||||||
|  |           showCopyButton: false, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('does not render copy button', () => { | ||||||
|  |       expect(findCopyButton().exists()).toBe(false); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('passes `formInputGroupProps` prop to `GlFormInputGroup`', () => { | ||||||
|  |     createComponent({ | ||||||
|  |       propsData: { | ||||||
|  |         formInputGroupProps: { | ||||||
|  |           label: 'Foo bar', | ||||||
|  |         }, | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     expect(findFormInputGroup().props('label')).toBe('Foo bar'); | ||||||
|  |   }); | ||||||
|  | 
 | ||||||
|  |   it('passes `copyButtonTitle` prop to `ClipboardButton`', () => { | ||||||
|  |     createComponent({ | ||||||
|  |       propsData: { | ||||||
|  |         copyButtonTitle: 'Copy token', | ||||||
|  |       }, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     expect(findCopyButton().props('title')).toBe('Copy token'); | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  | @ -90,18 +90,6 @@ RSpec.describe Gitlab::Database::Partitioning::DetachedPartitionDropper do | ||||||
|         expect(table_oid('test_partition')).to be_nil |         expect(table_oid('test_partition')).to be_nil | ||||||
|       end |       end | ||||||
| 
 | 
 | ||||||
|       context 'when the drop_detached_partitions feature flag is disabled' do |  | ||||||
|         before do |  | ||||||
|           stub_feature_flags(drop_detached_partitions: false) |  | ||||||
|         end |  | ||||||
| 
 |  | ||||||
|         it 'does not drop the partition' do |  | ||||||
|           dropper.perform |  | ||||||
| 
 |  | ||||||
|           expect(table_oid('test_partition')).not_to be_nil |  | ||||||
|         end |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       context 'removing foreign keys' do |       context 'removing foreign keys' do | ||||||
|         it 'removes foreign keys from the table before dropping it' do |         it 'removes foreign keys from the table before dropping it' do | ||||||
|           expect(dropper).to receive(:drop_detached_partition).and_wrap_original do |drop_method, partition_name| |           expect(dropper).to receive(:drop_detached_partition).and_wrap_original do |drop_method, partition_name| | ||||||
|  |  | ||||||
|  | @ -101,11 +101,6 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do | ||||||
|       ] |       ] | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     context 'with the partition_pruning feature flag enabled' do |  | ||||||
|       before do |  | ||||||
|         stub_feature_flags(partition_pruning: true) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|     it 'detaches each extra partition' do |     it 'detaches each extra partition' do | ||||||
|       extra_partitions.each { |p| expect(manager).to receive(:detach_one_partition).with(p) } |       extra_partitions.each { |p| expect(manager).to receive(:detach_one_partition).with(p) } | ||||||
| 
 | 
 | ||||||
|  | @ -113,19 +108,6 @@ RSpec.describe Gitlab::Database::Partitioning::PartitionManager do | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|     context 'with the partition_pruning feature flag disabled' do |  | ||||||
|       before do |  | ||||||
|         stub_feature_flags(partition_pruning: false) |  | ||||||
|       end |  | ||||||
| 
 |  | ||||||
|       it 'returns immediately' do |  | ||||||
|         expect(manager).not_to receive(:detach) |  | ||||||
| 
 |  | ||||||
|         sync_partitions |  | ||||||
|       end |  | ||||||
|     end |  | ||||||
|   end |  | ||||||
| 
 |  | ||||||
|   describe '#detach_partitions' do |   describe '#detach_partitions' do | ||||||
|     around do |ex| |     around do |ex| | ||||||
|       travel_to(Date.parse('2021-06-23')) do |       travel_to(Date.parse('2021-06-23')) do | ||||||
|  |  | ||||||
|  | @ -428,6 +428,26 @@ RSpec.describe API::Ci::Jobs do | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     context 'when job succeeded' do | ||||||
|  |       it 'does not return failure_reason' do | ||||||
|  |         get api("/projects/#{project.id}/jobs/#{job.id}", api_user) | ||||||
|  | 
 | ||||||
|  |         expect(json_response).not_to include('failure_reason') | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|  |     context 'when job failed' do | ||||||
|  |       let(:job) do | ||||||
|  |         create(:ci_build, :failed, :tags, pipeline: pipeline) | ||||||
|  |       end | ||||||
|  | 
 | ||||||
|  |       it 'returns failure_reason' do | ||||||
|  |         get api("/projects/#{project.id}/jobs/#{job.id}", api_user) | ||||||
|  | 
 | ||||||
|  |         expect(json_response).to include('failure_reason') | ||||||
|  |       end | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     context 'when trace artifact record exists with no stored file', :skip_before_request do |     context 'when trace artifact record exists with no stored file', :skip_before_request do | ||||||
|       before do |       before do | ||||||
|         create(:ci_job_artifact, :unarchived_trace_artifact, job: job, project: job.project) |         create(:ci_job_artifact, :unarchived_trace_artifact, job: job, project: job.project) | ||||||
|  |  | ||||||
|  | @ -125,14 +125,6 @@ RSpec.describe Ci::RetryBuildService do | ||||||
|         expect(new_build.needs_attributes).to match(build.needs_attributes) |         expect(new_build.needs_attributes).to match(build.needs_attributes) | ||||||
|         expect(new_build.needs).not_to match(build.needs) |         expect(new_build.needs).not_to match(build.needs) | ||||||
|       end |       end | ||||||
| 
 |  | ||||||
|       it 'clones only the job variables attributes' do |  | ||||||
|         expect(new_build.job_variables.exists?).to be_truthy |  | ||||||
|         expect(build.job_variables.exists?).to be_truthy |  | ||||||
| 
 |  | ||||||
|         expect(new_build.job_variables_attributes).to match(build.job_variables_attributes) |  | ||||||
|         expect(new_build.job_variables).not_to match(build.job_variables) |  | ||||||
|       end |  | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     describe 'reject accessors' do |     describe 'reject accessors' do | ||||||
|  | @ -155,7 +147,7 @@ RSpec.describe Ci::RetryBuildService do | ||||||
|         Ci::Build.attribute_names.map(&:to_sym) + |         Ci::Build.attribute_names.map(&:to_sym) + | ||||||
|         Ci::Build.attribute_aliases.keys.map(&:to_sym) + |         Ci::Build.attribute_aliases.keys.map(&:to_sym) + | ||||||
|         Ci::Build.reflect_on_all_associations.map(&:name) + |         Ci::Build.reflect_on_all_associations.map(&:name) + | ||||||
|         [:tag_list, :needs_attributes, :job_variables_attributes] - |         [:tag_list, :needs_attributes] - | ||||||
|         # ee-specific accessors should be tested in ee/spec/services/ci/retry_build_service_spec.rb instead |         # ee-specific accessors should be tested in ee/spec/services/ci/retry_build_service_spec.rb instead | ||||||
|         described_class.extra_accessors - |         described_class.extra_accessors - | ||||||
|         [:dast_site_profiles_build, :dast_scanner_profiles_build] # join tables |         [:dast_site_profiles_build, :dast_scanner_profiles_build] # join tables | ||||||
|  |  | ||||||
|  | @ -2,6 +2,7 @@ | ||||||
| 
 | 
 | ||||||
| require 'simplecov' | require 'simplecov' | ||||||
| require 'simplecov-cobertura' | require 'simplecov-cobertura' | ||||||
|  | require 'simplecov-lcov' | ||||||
| require_relative '../lib/gitlab/utils' | require_relative '../lib/gitlab/utils' | ||||||
| 
 | 
 | ||||||
| module SimpleCovEnv | module SimpleCovEnv | ||||||
|  | @ -18,10 +19,13 @@ module SimpleCovEnv | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def configure_formatter |   def configure_formatter | ||||||
|  |     SimpleCov::Formatter::LcovFormatter.config.report_with_single_file = true | ||||||
|  | 
 | ||||||
|     SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([ |     SimpleCov.formatters = SimpleCov::Formatter::MultiFormatter.new([ | ||||||
|       SimpleCov::Formatter::SimpleFormatter, |       SimpleCov::Formatter::SimpleFormatter, | ||||||
|       SimpleCov::Formatter::HTMLFormatter, |       SimpleCov::Formatter::HTMLFormatter, | ||||||
|       SimpleCov::Formatter::CoberturaFormatter |       SimpleCov::Formatter::CoberturaFormatter, | ||||||
|  |       SimpleCov::Formatter::LcovFormatter | ||||||
|     ]) |     ]) | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue