Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
		
							parent
							
								
									040df42a88
								
							
						
					
					
						commit
						b49ce524ed
					
				|  | @ -1,8 +1,10 @@ | ||||||
| <script> | <script> | ||||||
| import { GlBadge, GlTab, GlTabs } from '@gitlab/ui'; | import { GlBadge, GlPagination, GlTab, GlTabs } from '@gitlab/ui'; | ||||||
| import { __, s__ } from '~/locale'; | import { s__, __, sprintf } from '~/locale'; | ||||||
|  | import { updateHistory, setUrlParams, queryToObject } from '~/lib/utils/url_utility'; | ||||||
| import environmentAppQuery from '../graphql/queries/environment_app.query.graphql'; | import environmentAppQuery from '../graphql/queries/environment_app.query.graphql'; | ||||||
| import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql'; | import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql'; | ||||||
|  | import pageInfoQuery from '../graphql/queries/page_info.query.graphql'; | ||||||
| import EnvironmentFolder from './new_environment_folder.vue'; | import EnvironmentFolder from './new_environment_folder.vue'; | ||||||
| import EnableReviewAppModal from './enable_review_app_modal.vue'; | import EnableReviewAppModal from './enable_review_app_modal.vue'; | ||||||
| 
 | 
 | ||||||
|  | @ -11,6 +13,7 @@ export default { | ||||||
|     EnvironmentFolder, |     EnvironmentFolder, | ||||||
|     EnableReviewAppModal, |     EnableReviewAppModal, | ||||||
|     GlBadge, |     GlBadge, | ||||||
|  |     GlPagination, | ||||||
|     GlTab, |     GlTab, | ||||||
|     GlTabs, |     GlTabs, | ||||||
|   }, |   }, | ||||||
|  | @ -20,6 +23,7 @@ export default { | ||||||
|       variables() { |       variables() { | ||||||
|         return { |         return { | ||||||
|           scope: this.scope, |           scope: this.scope, | ||||||
|  |           page: this.page ?? 1, | ||||||
|         }; |         }; | ||||||
|       }, |       }, | ||||||
|       pollInterval() { |       pollInterval() { | ||||||
|  | @ -29,6 +33,9 @@ export default { | ||||||
|     interval: { |     interval: { | ||||||
|       query: pollIntervalQuery, |       query: pollIntervalQuery, | ||||||
|     }, |     }, | ||||||
|  |     pageInfo: { | ||||||
|  |       query: pageInfoQuery, | ||||||
|  |     }, | ||||||
|   }, |   }, | ||||||
|   inject: ['newEnvironmentPath', 'canCreateEnvironment'], |   inject: ['newEnvironmentPath', 'canCreateEnvironment'], | ||||||
|   i18n: { |   i18n: { | ||||||
|  | @ -36,11 +43,21 @@ export default { | ||||||
|     reviewAppButtonLabel: s__('Environments|Enable review app'), |     reviewAppButtonLabel: s__('Environments|Enable review app'), | ||||||
|     available: __('Available'), |     available: __('Available'), | ||||||
|     stopped: __('Stopped'), |     stopped: __('Stopped'), | ||||||
|  |     prevPage: __('Go to previous page'), | ||||||
|  |     nextPage: __('Go to next page'), | ||||||
|  |     next: __('Next'), | ||||||
|  |     prev: __('Prev'), | ||||||
|  |     goto: (page) => sprintf(__('Go to page %{page}'), { page }), | ||||||
|   }, |   }, | ||||||
|   modalId: 'enable-review-app-info', |   modalId: 'enable-review-app-info', | ||||||
|   data() { |   data() { | ||||||
|     const scope = new URLSearchParams(window.location.search).get('scope') || 'available'; |     const { page = '1', scope = 'available' } = queryToObject(window.location.search); | ||||||
|     return { interval: undefined, scope, isReviewAppModalVisible: false }; |     return { | ||||||
|  |       interval: undefined, | ||||||
|  |       isReviewAppModalVisible: false, | ||||||
|  |       page: parseInt(page, 10), | ||||||
|  |       scope, | ||||||
|  |     }; | ||||||
|   }, |   }, | ||||||
|   computed: { |   computed: { | ||||||
|     canSetupReviewApp() { |     canSetupReviewApp() { | ||||||
|  | @ -82,6 +99,19 @@ export default { | ||||||
|     stoppedCount() { |     stoppedCount() { | ||||||
|       return this.environmentApp?.stoppedCount; |       return this.environmentApp?.stoppedCount; | ||||||
|     }, |     }, | ||||||
|  |     totalItems() { | ||||||
|  |       return this.pageInfo?.total; | ||||||
|  |     }, | ||||||
|  |     itemsPerPage() { | ||||||
|  |       return this.pageInfo?.perPage; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   mounted() { | ||||||
|  |     window.addEventListener('popstate', this.syncPageFromQueryParams); | ||||||
|  |   }, | ||||||
|  |   destroyed() { | ||||||
|  |     window.removeEventListener('popstate', this.syncPageFromQueryParams); | ||||||
|  |     this.$apollo.queries.environmentApp.stopPolling(); | ||||||
|   }, |   }, | ||||||
|   methods: { |   methods: { | ||||||
|     showReviewAppModal() { |     showReviewAppModal() { | ||||||
|  | @ -89,12 +119,30 @@ export default { | ||||||
|     }, |     }, | ||||||
|     setScope(scope) { |     setScope(scope) { | ||||||
|       this.scope = scope; |       this.scope = scope; | ||||||
|  |       this.resetPolling(); | ||||||
|  |     }, | ||||||
|  |     movePage(direction) { | ||||||
|  |       this.moveToPage(this.pageInfo[`${direction}Page`]); | ||||||
|  |     }, | ||||||
|  |     moveToPage(page) { | ||||||
|  |       this.page = page; | ||||||
|  |       updateHistory({ | ||||||
|  |         url: setUrlParams({ page: this.page }), | ||||||
|  |         title: document.title, | ||||||
|  |       }); | ||||||
|  |       this.resetPolling(); | ||||||
|  |     }, | ||||||
|  |     syncPageFromQueryParams() { | ||||||
|  |       const { page = '1' } = queryToObject(window.location.search); | ||||||
|  |       this.page = parseInt(page, 10); | ||||||
|  |     }, | ||||||
|  |     resetPolling() { | ||||||
|       this.$apollo.queries.environmentApp.stopPolling(); |       this.$apollo.queries.environmentApp.stopPolling(); | ||||||
|       this.$nextTick(() => { |       this.$nextTick(() => { | ||||||
|         if (this.interval) { |         if (this.interval) { | ||||||
|           this.$apollo.queries.environmentApp.startPolling(this.interval); |           this.$apollo.queries.environmentApp.startPolling(this.interval); | ||||||
|         } else { |         } else { | ||||||
|           this.$apollo.queries.environmentApp.refetch({ scope }); |           this.$apollo.queries.environmentApp.refetch({ scope: this.scope, page: this.page }); | ||||||
|         } |         } | ||||||
|       }); |       }); | ||||||
|     }, |     }, | ||||||
|  | @ -139,5 +187,19 @@ export default { | ||||||
|       class="gl-mb-3" |       class="gl-mb-3" | ||||||
|       :nested-environment="folder" |       :nested-environment="folder" | ||||||
|     /> |     /> | ||||||
|  |     <gl-pagination | ||||||
|  |       align="center" | ||||||
|  |       :total-items="totalItems" | ||||||
|  |       :per-page="itemsPerPage" | ||||||
|  |       :value="page" | ||||||
|  |       :next="$options.i18n.next" | ||||||
|  |       :prev="$options.i18n.prev" | ||||||
|  |       :label-previous-page="$options.prevPage" | ||||||
|  |       :label-next-page="$options.nextPage" | ||||||
|  |       :label-page="$options.goto" | ||||||
|  |       @next="movePage('next')" | ||||||
|  |       @previous="movePage('previous')" | ||||||
|  |       @input="moveToPage" | ||||||
|  |     /> | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
|  | @ -1,6 +1,7 @@ | ||||||
| import VueApollo from 'vue-apollo'; | import VueApollo from 'vue-apollo'; | ||||||
| import createDefaultClient from '~/lib/graphql'; | import createDefaultClient from '~/lib/graphql'; | ||||||
| import environmentApp from './queries/environment_app.query.graphql'; | import environmentApp from './queries/environment_app.query.graphql'; | ||||||
|  | import pageInfoQuery from './queries/page_info.query.graphql'; | ||||||
| import { resolvers } from './resolvers'; | import { resolvers } from './resolvers'; | ||||||
| import typeDefs from './typedefs.graphql'; | import typeDefs from './typedefs.graphql'; | ||||||
| 
 | 
 | ||||||
|  | @ -19,6 +20,19 @@ export const apolloProvider = (endpoint) => { | ||||||
|       stoppedCount: 0, |       stoppedCount: 0, | ||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   cache.writeQuery({ | ||||||
|  |     query: pageInfoQuery, | ||||||
|  |     data: { | ||||||
|  |       pageInfo: { | ||||||
|  |         total: 0, | ||||||
|  |         perPage: 20, | ||||||
|  |         nextPage: 0, | ||||||
|  |         previousPage: 0, | ||||||
|  |         __typename: 'LocalPageInfo', | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|  |   }); | ||||||
|   return new VueApollo({ |   return new VueApollo({ | ||||||
|     defaultClient, |     defaultClient, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| query getEnvironmentApp($scope: String) { | query getEnvironmentApp($page: Int, $scope: String) { | ||||||
|   environmentApp(scope: $scope) @client { |   environmentApp(page: $page, scope: $scope) @client { | ||||||
|     availableCount |     availableCount | ||||||
|     stoppedCount |     stoppedCount | ||||||
|     environments |     environments | ||||||
|  |  | ||||||
|  | @ -0,0 +1,8 @@ | ||||||
|  | query getPageInfo { | ||||||
|  |   pageInfo @client { | ||||||
|  |     total | ||||||
|  |     perPage | ||||||
|  |     nextPage | ||||||
|  |     previousPage | ||||||
|  |   } | ||||||
|  | } | ||||||
|  | @ -1,9 +1,15 @@ | ||||||
| import axios from '~/lib/utils/axios_utils'; | import axios from '~/lib/utils/axios_utils'; | ||||||
| import { s__ } from '~/locale'; | import { s__ } from '~/locale'; | ||||||
| import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; | import { | ||||||
|  |   convertObjectPropsToCamelCase, | ||||||
|  |   parseIntPagination, | ||||||
|  |   normalizeHeaders, | ||||||
|  | } from '~/lib/utils/common_utils'; | ||||||
|  | 
 | ||||||
| import pollIntervalQuery from './queries/poll_interval.query.graphql'; | import pollIntervalQuery from './queries/poll_interval.query.graphql'; | ||||||
| import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql'; | import environmentToRollbackQuery from './queries/environment_to_rollback.query.graphql'; | ||||||
| import environmentToDeleteQuery from './queries/environment_to_delete.query.graphql'; | import environmentToDeleteQuery from './queries/environment_to_delete.query.graphql'; | ||||||
|  | import pageInfoQuery from './queries/page_info.query.graphql'; | ||||||
| 
 | 
 | ||||||
| const buildErrors = (errors = []) => ({ | const buildErrors = (errors = []) => ({ | ||||||
|   errors, |   errors, | ||||||
|  | @ -21,9 +27,11 @@ const mapEnvironment = (env) => ({ | ||||||
| 
 | 
 | ||||||
| export const resolvers = (endpoint) => ({ | export const resolvers = (endpoint) => ({ | ||||||
|   Query: { |   Query: { | ||||||
|     environmentApp(_context, { scope }, { cache }) { |     environmentApp(_context, { page, scope }, { cache }) { | ||||||
|       return axios.get(endpoint, { params: { nested: true, scope } }).then((res) => { |       return axios.get(endpoint, { params: { nested: true, page, scope } }).then((res) => { | ||||||
|         const interval = res.headers['poll-interval']; |         const headers = normalizeHeaders(res.headers); | ||||||
|  |         const interval = headers['POLL-INTERVAL']; | ||||||
|  |         const pageInfo = { ...parseIntPagination(headers), __typename: 'LocalPageInfo' }; | ||||||
| 
 | 
 | ||||||
|         if (interval) { |         if (interval) { | ||||||
|           cache.writeQuery({ query: pollIntervalQuery, data: { interval: parseFloat(interval) } }); |           cache.writeQuery({ query: pollIntervalQuery, data: { interval: parseFloat(interval) } }); | ||||||
|  | @ -31,6 +39,11 @@ export const resolvers = (endpoint) => ({ | ||||||
|           cache.writeQuery({ query: pollIntervalQuery, data: { interval: undefined } }); |           cache.writeQuery({ query: pollIntervalQuery, data: { interval: undefined } }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         cache.writeQuery({ | ||||||
|  |           query: pageInfoQuery, | ||||||
|  |           data: { pageInfo }, | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|         return { |         return { | ||||||
|           availableCount: res.data.available_count, |           availableCount: res.data.available_count, | ||||||
|           environments: res.data.environments.map(mapNestedEnvironment), |           environments: res.data.environments.map(mapNestedEnvironment), | ||||||
|  |  | ||||||
|  | @ -55,10 +55,18 @@ type LocalErrors { | ||||||
|   errors: [String!]! |   errors: [String!]! | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | type LocalPageInfo { | ||||||
|  |   total: Int! | ||||||
|  |   perPage: Int! | ||||||
|  |   nextPage: Int! | ||||||
|  |   previousPage: Int! | ||||||
|  | } | ||||||
|  | 
 | ||||||
| extend type Query { | extend type Query { | ||||||
|   environmentApp: LocalEnvironmentApp |   environmentApp(page: Int, scope: String): LocalEnvironmentApp | ||||||
|   folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder |   folder(environment: NestedLocalEnvironmentInput): LocalEnvironmentFolder | ||||||
|   environmentToDelete: LocalEnvironment |   environmentToDelete: LocalEnvironment | ||||||
|  |   pageInfo: LocalPageInfo | ||||||
|   environmentToRollback: LocalEnvironment |   environmentToRollback: LocalEnvironment | ||||||
|   isLastDeployment: Boolean |   isLastDeployment: Boolean | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ export const BV_HIDE_MODAL = 'bv::hide::modal'; | ||||||
| export const BV_HIDE_TOOLTIP = 'bv::hide::tooltip'; | export const BV_HIDE_TOOLTIP = 'bv::hide::tooltip'; | ||||||
| export const BV_DROPDOWN_SHOW = 'bv::dropdown::show'; | export const BV_DROPDOWN_SHOW = 'bv::dropdown::show'; | ||||||
| export const BV_DROPDOWN_HIDE = 'bv::dropdown::hide'; | export const BV_DROPDOWN_HIDE = 'bv::dropdown::hide'; | ||||||
|  | export const BV_COLLAPSE_STATE = 'bv::collapse::state'; | ||||||
| 
 | 
 | ||||||
| export const DEFAULT_TH_CLASSES = | export const DEFAULT_TH_CLASSES = | ||||||
|   'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!'; |   'gl-bg-transparent! gl-border-b-solid! gl-border-b-gray-100! gl-p-5! gl-border-b-1!'; | ||||||
|  |  | ||||||
|  | @ -45,7 +45,7 @@ | ||||||
|   = render 'shared/user_dropdown_instance_review' |   = render 'shared/user_dropdown_instance_review' | ||||||
|   - if Gitlab.com_but_not_canary? |   - if Gitlab.com_but_not_canary? | ||||||
|     %li.d-md-none |     %li.d-md-none | ||||||
|       = link_to _("Switch to GitLab Next"), "https://next.gitlab.com/" |       = link_to _("Switch to GitLab Next"), Gitlab::Saas.canary_toggle_com_url | ||||||
| 
 | 
 | ||||||
|   - if current_user_menu?(:sign_out) |   - if current_user_menu?(:sign_out) | ||||||
|     %li.divider |     %li.divider | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ | ||||||
|               %span.logo-text.d-none.d-lg-block.gl-ml-3 |               %span.logo-text.d-none.d-lg-block.gl-ml-3 | ||||||
|                 = logo_text |                 = logo_text | ||||||
|           - if Gitlab.com_and_canary? |           - if Gitlab.com_and_canary? | ||||||
|             = link_to 'https://next.gitlab.com', class: 'canary-badge bg-transparent', data: { qa_selector: 'canary_badge_link' }, target: :_blank, rel: :_noopener do |             = link_to Gitlab::Saas.canary_toggle_com_url, class: 'canary-badge bg-transparent', data: { qa_selector: 'canary_badge_link' }, target: :_blank, rel: 'noopener noreferrer' do | ||||||
|               %span.gl-badge.gl-bg-green-500.gl-text-white.gl-rounded-pill.gl-font-weight-bold.gl-py-1 |               %span.gl-badge.gl-bg-green-500.gl-text-white.gl-rounded-pill.gl-font-weight-bold.gl-py-1 | ||||||
|                 = _('Next') |                 = _('Next') | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -20,4 +20,4 @@ | ||||||
|   = render 'shared/user_dropdown_instance_review' |   = render 'shared/user_dropdown_instance_review' | ||||||
|   - if Gitlab.com_but_not_canary? |   - if Gitlab.com_but_not_canary? | ||||||
|     %li |     %li | ||||||
|       = link_to _("Switch to GitLab Next"), "https://next.gitlab.com/" |       = link_to _("Switch to GitLab Next"), Gitlab::Saas.canary_toggle_com_url | ||||||
|  |  | ||||||
|  | @ -10,9 +10,7 @@ | ||||||
|     .card-header |     .card-header | ||||||
|       %strong |       %strong | ||||||
|         = s_('PrometheusService|Custom metrics') |         = s_('PrometheusService|Custom metrics') | ||||||
|       -# haml-lint:disable NoPlainNodes |       = gl_badge_tag 0, nil, class: 'js-custom-monitored-count' | ||||||
|       %span.badge.badge-pill.js-custom-monitored-count 0 |  | ||||||
|       -# haml-lint:enable NoPlainNodes |  | ||||||
|       = link_to s_('PrometheusService|New metric'), new_project_prometheus_metric_path(project), class: 'btn gl-button btn-confirm gl-ml-auto js-new-metric-button hidden', data: { qa_selector: 'new_metric_button' } |       = link_to s_('PrometheusService|New metric'), new_project_prometheus_metric_path(project), class: 'btn gl-button btn-confirm gl-ml-auto js-new-metric-button hidden', data: { qa_selector: 'new_metric_button' } | ||||||
|     .card-body |     .card-body | ||||||
|       .flash-container.hidden |       .flash-container.hidden | ||||||
|  |  | ||||||
|  | @ -12,7 +12,7 @@ | ||||||
|     .card-header |     .card-header | ||||||
|       %strong |       %strong | ||||||
|         = s_('PrometheusService|Common metrics') |         = s_('PrometheusService|Common metrics') | ||||||
|       %span.badge.badge-pill.js-monitored-count 0 |       = gl_badge_tag 0, nil, class: 'js-monitored-count' | ||||||
|     .card-body |     .card-body | ||||||
|       .loading-metrics.js-loading-metrics |       .loading-metrics.js-loading-metrics | ||||||
|         %p.m-3 |         %p.m-3 | ||||||
|  | @ -28,7 +28,7 @@ | ||||||
|       = sprite_icon('chevron-lg-right', css_class: 'panel-toggle js-panel-toggle-right' ) |       = sprite_icon('chevron-lg-right', css_class: 'panel-toggle js-panel-toggle-right' ) | ||||||
|       = sprite_icon('chevron-lg-down', css_class: 'panel-toggle js-panel-toggle-down hidden' ) |       = sprite_icon('chevron-lg-down', css_class: 'panel-toggle js-panel-toggle-down hidden' ) | ||||||
|       = s_('PrometheusService|Missing environment variable') |       = s_('PrometheusService|Missing environment variable') | ||||||
|       %span.badge.badge-pill.js-env-var-count 0 |       = gl_badge_tag 0, nil, class: 'js-env-var-count' | ||||||
|     .card-body.hidden |     .card-body.hidden | ||||||
|       .flash-container |       .flash-container | ||||||
|         .flash-notice |         .flash-notice | ||||||
|  |  | ||||||
|  | @ -1,15 +1,12 @@ | ||||||
| -  count_badge_classes = 'badge badge-muted badge-pill gl-badge gl-tab-counter-badge sm gl-display-none gl-sm-display-inline-flex' | -  count_badge_classes = 'gl-display-none gl-sm-display-inline-flex' | ||||||
| 
 | 
 | ||||||
| = gl_tabs_nav( {class: 'gl-border-b-0 gl-flex-grow-1', data: { testid: 'milestones-filter' } } ) do | = gl_tabs_nav( {class: 'gl-border-b-0 gl-flex-grow-1', data: { testid: 'milestones-filter' } } ) do | ||||||
|   = gl_tab_link_to milestones_filter_path(state: 'opened'), { item_active: params[:state].blank? || params[:state] == 'opened' } do |   = gl_tab_link_to milestones_filter_path(state: 'opened'), { item_active: params[:state].blank? || params[:state] == 'opened' } do | ||||||
|     = _('Open') |     = _('Open') | ||||||
|     %span{ class: count_badge_classes } |     = gl_tab_counter_badge counts[:opened], { class: count_badge_classes } | ||||||
|       = counts[:opened] |  | ||||||
|   = gl_tab_link_to milestones_filter_path(state: 'closed', sort: 'due_date_desc'), { item_active: params[:state] == 'closed' } do |   = gl_tab_link_to milestones_filter_path(state: 'closed', sort: 'due_date_desc'), { item_active: params[:state] == 'closed' } do | ||||||
|     = _('Closed') |     = _('Closed') | ||||||
|     %span{ class: count_badge_classes } |     = gl_tab_counter_badge counts[:closed], { class: count_badge_classes } | ||||||
|       = counts[:closed] |  | ||||||
|   = gl_tab_link_to milestones_filter_path(state: 'all', sort: 'due_date_desc'), { item_active: params[:state] == 'all' } do |   = gl_tab_link_to milestones_filter_path(state: 'all', sort: 'due_date_desc'), { item_active: params[:state] == 'all' } do | ||||||
|     = _('All') |     = _('All') | ||||||
|     %span{ class: count_badge_classes } |     = gl_tab_counter_badge counts[:all], { class: count_badge_classes } | ||||||
|       = counts[:all] |  | ||||||
|  |  | ||||||
|  | @ -0,0 +1,21 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | class RemoveVulnerabilityFindingLinksAgain < Gitlab::Database::Migration[1.0] | ||||||
|  |   BATCH_SIZE = 50_000 | ||||||
|  |   MIGRATION = 'RemoveVulnerabilityFindingLinks' | ||||||
|  | 
 | ||||||
|  |   disable_ddl_transaction! | ||||||
|  | 
 | ||||||
|  |   def up | ||||||
|  |     queue_background_migration_jobs_by_range_at_intervals( | ||||||
|  |       define_batchable_model('vulnerability_finding_links'), | ||||||
|  |       MIGRATION, | ||||||
|  |       2.minutes, | ||||||
|  |       batch_size: BATCH_SIZE | ||||||
|  |     ) | ||||||
|  |   end | ||||||
|  | 
 | ||||||
|  |   def down | ||||||
|  |     # no ops | ||||||
|  |   end | ||||||
|  | end | ||||||
|  | @ -0,0 +1 @@ | ||||||
|  | 9bbd4c3e396e0de130418e705a370ce629ca507c82fa2ff5bbf085cdf01c2ff3 | ||||||
|  | @ -35,9 +35,9 @@ verification methods: | ||||||
| | Git      | Project Snippets                                | Geo with Gitaly                       | Gitaly Checksum        | | | Git      | Project Snippets                                | Geo with Gitaly                       | Gitaly Checksum        | | ||||||
| | Git      | Personal Snippets                               | Geo with Gitaly                       | Gitaly Checksum        | | | Git      | Personal Snippets                               | Geo with Gitaly                       | Gitaly Checksum        | | ||||||
| | Git      | Group wiki repository                           | Geo with Gitaly                       | _Not implemented_      | | | Git      | Group wiki repository                           | Geo with Gitaly                       | _Not implemented_      | | ||||||
| | Blobs    | User uploads _(file system)_                    | Geo with API                          | _Not implemented_      | | | Blobs    | User uploads _(file system)_                    | Geo with API                          | SHA256 checksum        | | ||||||
| | Blobs    | User uploads _(object storage)_                 | Geo with API/Managed (*2*)            | _Not implemented_      | | | Blobs    | User uploads _(object storage)_                 | Geo with API/Managed (*2*)            | _Not implemented_      | | ||||||
| | Blobs    | LFS objects _(file system)_                     | Geo with API                          | SHA256 checksum      | | | Blobs    | LFS objects _(file system)_                     | Geo with API                          | SHA256 checksum        | | ||||||
| | Blobs    | LFS objects _(object storage)_                  | Geo with API/Managed (*2*)            | _Not implemented_      | | | Blobs    | LFS objects _(object storage)_                  | Geo with API/Managed (*2*)            | _Not implemented_      | | ||||||
| | Blobs    | CI job artifacts _(file system)_                | Geo with API                          | _Not implemented_      | | | Blobs    | CI job artifacts _(file system)_                | Geo with API                          | _Not implemented_      | | ||||||
| | Blobs    | CI job artifacts _(object storage)_             | Geo with API/Managed (*2*)            | _Not implemented_      | | | Blobs    | CI job artifacts _(object storage)_             | Geo with API/Managed (*2*)            | _Not implemented_      | | ||||||
|  | @ -188,8 +188,8 @@ successfully, you must replicate their data using some other means. | ||||||
| |[Project repository](../../../user/project/repository/)                                                        | **Yes** (10.2)                                                          | **Yes** (10.7)                                                             | No                                                                            |       | | |[Project repository](../../../user/project/repository/)                                                        | **Yes** (10.2)                                                          | **Yes** (10.7)                                                             | No                                                                            |       | | ||||||
| |[Project wiki repository](../../../user/project/wiki/)                                                         | **Yes** (10.2)                                                          | **Yes** (10.7)                                                             | No                                                                            |       | | |[Project wiki repository](../../../user/project/wiki/)                                                         | **Yes** (10.2)                                                          | **Yes** (10.7)                                                             | No                                                                            |       | | ||||||
| |[Group wiki repository](../../../user/project/wiki/group.md)                                                   | [**Yes** (13.10)](https://gitlab.com/gitlab-org/gitlab/-/issues/208147) | No                                                                         | No                                                                            | Behind feature flag `geo_group_wiki_repository_replication`, enabled by default. | | |[Group wiki repository](../../../user/project/wiki/group.md)                                                   | [**Yes** (13.10)](https://gitlab.com/gitlab-org/gitlab/-/issues/208147) | No                                                                         | No                                                                            | Behind feature flag `geo_group_wiki_repository_replication`, enabled by default. | | ||||||
| |[Uploads](../../uploads.md)                                                                                    | **Yes** (10.2)                                                          | [No](https://gitlab.com/groups/gitlab-org/-/epics/1817)                    | No                                                                            | Verified only on transfer or manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. | | |[Uploads](../../uploads.md)                                                                                    | **Yes** (10.2)                                                          | **Yes** (14.6)                                                             | Via Object Storage provider if supported. Native Geo support (Beta).          | Replication is behind the feature flag `geo_upload_replication`, enabled by default. Verification is behind the feature flag `geo_upload_verification` introduced and enabled by default in 14.6. | | ||||||
| |[LFS objects](../../lfs/index.md)                                                                              | **Yes** (10.2)                                                          | **Yes**(14.6)                   | Via Object Storage provider if supported. Native Geo support (Beta).          | GitLab versions 11.11.x and 12.0.x are affected by [a bug that prevents any new LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696).<br /><br />Replication is behind the feature flag `geo_lfs_object_replication`, enabled by default. Verification is behind the feature flag `geo_lfs_object_verification` enabled by default in 14.6. | | |[LFS objects](../../lfs/index.md)                                                                              | **Yes** (10.2)                                                          | **Yes** (14.6)                                                             | Via Object Storage provider if supported. Native Geo support (Beta).          | GitLab versions 11.11.x and 12.0.x are affected by [a bug that prevents any new LFS objects from replicating](https://gitlab.com/gitlab-org/gitlab/-/issues/32696).<br /><br />Replication is behind the feature flag `geo_lfs_object_replication`, enabled by default. Verification is behind the feature flag `geo_lfs_object_verification` introduced and enabled by default in 14.6. | | ||||||
| |[Personal snippets](../../../user/snippets.md)                                                                 | **Yes** (10.2)                                                          | **Yes** (10.2)                                                             | No                                                                            |       | | |[Personal snippets](../../../user/snippets.md)                                                                 | **Yes** (10.2)                                                          | **Yes** (10.2)                                                             | No                                                                            |       | | ||||||
| |[Project snippets](../../../user/snippets.md)                                                                  | **Yes** (10.2)                                                          | **Yes** (10.2)                                                             | No                                                                            |       | | |[Project snippets](../../../user/snippets.md)                                                                  | **Yes** (10.2)                                                          | **Yes** (10.2)                                                             | No                                                                            |       | | ||||||
| |[CI job artifacts](../../../ci/pipelines/job_artifacts.md)                                                     | **Yes** (10.4)                                                          | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923)                   | Via Object Storage provider if supported. Native Geo support (Beta).          | Verified only manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. Job logs also verified on transfer. | | |[CI job artifacts](../../../ci/pipelines/job_artifacts.md)                                                     | **Yes** (10.4)                                                          | [No](https://gitlab.com/gitlab-org/gitlab/-/issues/8923)                   | Via Object Storage provider if supported. Native Geo support (Beta).          | Verified only manually using [Integrity Check Rake Task](../../raketasks/check.md) on both sites and comparing the output between them. Job logs also verified on transfer. | | ||||||
|  |  | ||||||
|  | @ -16,6 +16,8 @@ This document lists the configuration options for your GitLab `.gitlab-ci.yml` f | ||||||
| When you are editing your `.gitlab-ci.yml` file, you can validate it with the | When you are editing your `.gitlab-ci.yml` file, you can validate it with the | ||||||
| [CI Lint](../lint.md) tool. | [CI Lint](../lint.md) tool. | ||||||
| 
 | 
 | ||||||
|  | If you are editing this page, make sure you follow the [CI/CD YAML reference style guide](../../development/cicd/cicd_reference_documentation_guide.md). | ||||||
|  | 
 | ||||||
| ## Keywords | ## Keywords | ||||||
| 
 | 
 | ||||||
| A GitLab CI/CD pipeline configuration includes: | A GitLab CI/CD pipeline configuration includes: | ||||||
|  |  | ||||||
|  | @ -33,9 +33,14 @@ Try to avoid using **above** when referring to an example or table in a document | ||||||
| 
 | 
 | ||||||
| Do not use **above** when referring to versions of the product. Use [**later**](#later) instead. | Do not use **above** when referring to versions of the product. Use [**later**](#later) instead. | ||||||
| 
 | 
 | ||||||
| - Do: In GitLab 14.4 and later... | Use: | ||||||
| - Do not: In GitLab 14.4 and above... | 
 | ||||||
| - Do not: In GitLab 14.4 and higher... | - In GitLab 14.4 and later... | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - In GitLab 14.4 and above... | ||||||
|  | - In GitLab 14.4 and higher... | ||||||
| 
 | 
 | ||||||
| ## access level | ## access level | ||||||
| 
 | 
 | ||||||
|  | @ -56,9 +61,14 @@ To view the administrator access level, in the GitLab UI, go to the Admin Area a | ||||||
| 
 | 
 | ||||||
| An **administrator** is not a [role](#roles) or [permission](#permissions). | An **administrator** is not a [role](#roles) or [permission](#permissions). | ||||||
| 
 | 
 | ||||||
| - Do: To do this thing, you must be an administrator. | Use: | ||||||
| - Do: To do this thing, you must have the administrator access level. | 
 | ||||||
| - Do not: To do this thing, you must have the Admin role. | - To do this thing, you must be an administrator. | ||||||
|  | - To do this thing, you must have the administrator access level. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - To do this thing, you must have the Admin role. | ||||||
| 
 | 
 | ||||||
| ## Admin Area | ## Admin Area | ||||||
| 
 | 
 | ||||||
|  | @ -67,10 +77,16 @@ This area of the UI says **Admin Area** at the top of the page and on the menu. | ||||||
| 
 | 
 | ||||||
| ## allow, enable | ## allow, enable | ||||||
| 
 | 
 | ||||||
| Try to avoid **allow** and **enable**, unless you are talking about security-related features. For example: | Try to avoid **allow** and **enable**, unless you are talking about security-related features. | ||||||
| 
 | 
 | ||||||
| - Do: Use this feature to create a pipeline. | Use: | ||||||
| - Do not: This feature allows you to create a pipeline. | 
 | ||||||
|  | - You can add a file to your repository. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - This feature allows you to add a file to your repository. | ||||||
|  | - This feature enables users to add files to their repository. | ||||||
| 
 | 
 | ||||||
| This phrasing is more active and is from the user perspective, rather than the person who implemented the feature. | This phrasing is more active and is from the user perspective, rather than the person who implemented the feature. | ||||||
| [View details in the Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/a/allow-allows). | [View details in the Microsoft style guide](https://docs.microsoft.com/en-us/style-guide/a-z-word-list-term-collections/a/allow-allows). | ||||||
|  | @ -126,8 +142,13 @@ Use **text box** to refer to the UI field. Do not use **field** or **box**. For | ||||||
| 
 | 
 | ||||||
| Don't use a descriptor with **button**. | Don't use a descriptor with **button**. | ||||||
| 
 | 
 | ||||||
| - Do: Select **Run pipelines**. | Use: | ||||||
| - Do not: Select the **Run pipelines** button. | 
 | ||||||
|  | - Select **Run pipelines**. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - Select the **Run pipelines** button. | ||||||
| 
 | 
 | ||||||
| ## cannot, can not | ## cannot, can not | ||||||
| 
 | 
 | ||||||
|  | @ -194,8 +215,8 @@ When writing about the Developer role: | ||||||
| - Do not use the phrase, **if you are a developer** to mean someone who is assigned the Developer | - Do not use the phrase, **if you are a developer** to mean someone who is assigned the Developer | ||||||
|   role. Instead, write it out. For example, **if you are assigned the Developer role**. |   role. Instead, write it out. For example, **if you are assigned the Developer role**. | ||||||
| - To describe a situation where the Developer role is the minimum required: | - To describe a situation where the Developer role is the minimum required: | ||||||
|   - Do: at least the Developer role |   - Use: at least the Developer role | ||||||
|   - Do not: the Developer role or higher |   - Instead of: the Developer role or higher | ||||||
| 
 | 
 | ||||||
| Do not use **Developer permissions**. A user who is assigned the Developer role has a set of associated permissions. | Do not use **Developer permissions**. A user who is assigned the Developer role has a set of associated permissions. | ||||||
| 
 | 
 | ||||||
|  | @ -217,8 +238,13 @@ For example: | ||||||
| 
 | 
 | ||||||
| Use **earlier** when talking about version numbers. | Use **earlier** when talking about version numbers. | ||||||
| 
 | 
 | ||||||
| - Do: In GitLab 14.1 and earlier. | Use: | ||||||
| - Do not: In GitLab 14.1 and lower. | 
 | ||||||
|  | - In GitLab 14.1 and earlier. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - In GitLab 14.1 and lower. | ||||||
| 
 | 
 | ||||||
| ## easily | ## easily | ||||||
| 
 | 
 | ||||||
|  | @ -254,8 +280,13 @@ Use lowercase for **epic board**. | ||||||
| Try to avoid **etc.**. Be as specific as you can. Do not use | Try to avoid **etc.**. Be as specific as you can. Do not use | ||||||
| [**and so on**](#and-so-on) as a replacement. | [**and so on**](#and-so-on) as a replacement. | ||||||
| 
 | 
 | ||||||
| - Do: You can update objects, like merge requests and issues. | Use: | ||||||
| - Do not: You can update objects, like merge requests, issues, etc. | 
 | ||||||
|  | - You can update objects, like merge requests and issues. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - You can update objects, like merge requests, issues, etc. | ||||||
| 
 | 
 | ||||||
| ## expand | ## expand | ||||||
| 
 | 
 | ||||||
|  | @ -265,8 +296,13 @@ Use **expand** instead of **open** when you are talking about expanding or colla | ||||||
| 
 | 
 | ||||||
| Use **box** instead of **field** or **text box**. | Use **box** instead of **field** or **text box**. | ||||||
| 
 | 
 | ||||||
| - Do: In the **Variable name** box, enter `my text`. | Use: | ||||||
| - Do not: In the **Variable name** field, enter `my text`. | 
 | ||||||
|  | - In the **Variable name** box, enter `my text`. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - In the **Variable name** field, enter `my text`. | ||||||
| 
 | 
 | ||||||
| However, you can make an exception when you are writing a task and you need to refer to all | However, you can make an exception when you are writing a task and you need to refer to all | ||||||
| of the fields at once. For example: | of the fields at once. For example: | ||||||
|  | @ -320,8 +356,8 @@ When writing about the Guest role: | ||||||
| - Do not use the phrase, **if you are a guest** to mean someone who is assigned the Guest | - Do not use the phrase, **if you are a guest** to mean someone who is assigned the Guest | ||||||
|   role. Instead, write it out. For example, **if you are assigned the Guest role**. |   role. Instead, write it out. For example, **if you are assigned the Guest role**. | ||||||
| - To describe a situation where the Guest role is the minimum required: | - To describe a situation where the Guest role is the minimum required: | ||||||
|   - Do: at least the Guest role |   - Use: at least the Guest role | ||||||
|   - Do not: the Guest role or higher |   - Instead of: the Guest role or higher | ||||||
| 
 | 
 | ||||||
| Do not use **Guest permissions**. A user who is assigned the Guest role has a set of associated permissions. | Do not use **Guest permissions**. A user who is assigned the Guest role has a set of associated permissions. | ||||||
| 
 | 
 | ||||||
|  | @ -337,16 +373,26 @@ Do not use **high availability** or **HA**. Instead, direct readers to the GitLa | ||||||
| 
 | 
 | ||||||
| Do not use **higher** when talking about version numbers. | Do not use **higher** when talking about version numbers. | ||||||
| 
 | 
 | ||||||
| - Do: In GitLab 14.4 and later... | Use: | ||||||
| - Do not: In GitLab 14.4 and higher... | 
 | ||||||
| - Do not: In GitLab 14.4 and above... | - In GitLab 14.4 and later... | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - In GitLab 14.4 and higher... | ||||||
|  | - In GitLab 14.4 and above... | ||||||
| 
 | 
 | ||||||
| ## hit | ## hit | ||||||
| 
 | 
 | ||||||
| Don't use **hit** to mean **press**. | Don't use **hit** to mean **press**. | ||||||
| 
 | 
 | ||||||
| - Do: Press **ENTER**. | Use: | ||||||
| - Do not: Hit the **ENTER** button. | 
 | ||||||
|  | - Press **ENTER**. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - Hit the **ENTER** button. | ||||||
| 
 | 
 | ||||||
| ## I | ## I | ||||||
| 
 | 
 | ||||||
|  | @ -395,9 +441,14 @@ Do not use: | ||||||
| 
 | 
 | ||||||
| Use **later** when talking about version numbers. | Use **later** when talking about version numbers. | ||||||
| 
 | 
 | ||||||
| - Do: In GitLab 14.1 and later... | Use: | ||||||
| - Do not: In GitLab 14.1 and higher... | 
 | ||||||
| - Do not: In GitLab 14.1 and above... | - In GitLab 14.1 and later... | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - In GitLab 14.1 and higher... | ||||||
|  | - In GitLab 14.1 and above... | ||||||
| 
 | 
 | ||||||
| ## list | ## list | ||||||
| 
 | 
 | ||||||
|  | @ -412,8 +463,13 @@ Do not use **log in** or **log on**. Use [sign in](#sign-in) instead. If the use | ||||||
| 
 | 
 | ||||||
| Do not use **lower** when talking about version numbers. | Do not use **lower** when talking about version numbers. | ||||||
| 
 | 
 | ||||||
| - Do: In GitLab 14.1 and earlier. | Use: | ||||||
| - Do not: In GitLab 14.1 and lower. | 
 | ||||||
|  | - In GitLab 14.1 and earlier. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - In GitLab 14.1 and lower. | ||||||
| 
 | 
 | ||||||
| ## Maintainer | ## Maintainer | ||||||
| 
 | 
 | ||||||
|  | @ -424,8 +480,8 @@ When writing about the Maintainer role: | ||||||
| - Do not use the phrase, **if you are a maintainer** to mean someone who is assigned the Maintainer | - Do not use the phrase, **if you are a maintainer** to mean someone who is assigned the Maintainer | ||||||
|   role. Instead, write it out. For example, **if you are assigned the Maintainer role**. |   role. Instead, write it out. For example, **if you are assigned the Maintainer role**. | ||||||
| - To describe a situation where the Maintainer role is the minimum required: | - To describe a situation where the Maintainer role is the minimum required: | ||||||
|   - Do: at least the Maintainer role |   - Use: at least the Maintainer role | ||||||
|   - Do not: the Maintainer role or higher |   - Instead of: the Maintainer role or higher | ||||||
| 
 | 
 | ||||||
| Do not use **Maintainer permissions**. A user who is assigned the Maintainer role has a set of associated permissions. | Do not use **Maintainer permissions**. A user who is assigned the Maintainer role has a set of associated permissions. | ||||||
| 
 | 
 | ||||||
|  | @ -468,8 +524,14 @@ Do not use **navigate**. Use **go** instead. For example: | ||||||
| 
 | 
 | ||||||
| Try to avoid **needs to**, because it's wordy. Avoid **should** when you can be more specific. If something is required, use **must**. | Try to avoid **needs to**, because it's wordy. Avoid **should** when you can be more specific. If something is required, use **must**. | ||||||
| 
 | 
 | ||||||
| - Do: You must set the variable. Or: Set the variable. | Use: | ||||||
| - Do not: You need to set the variable. | 
 | ||||||
|  | - You must set the variable. | ||||||
|  | - Set the variable. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - You need to set the variable. | ||||||
| 
 | 
 | ||||||
| **Should** is acceptable for recommended actions or items, or in cases where an event may not | **Should** is acceptable for recommended actions or items, or in cases where an event may not | ||||||
| happen. For example: | happen. For example: | ||||||
|  | @ -483,22 +545,37 @@ happen. For example: | ||||||
| 
 | 
 | ||||||
| Do not use **note that** because it's wordy. | Do not use **note that** because it's wordy. | ||||||
| 
 | 
 | ||||||
| - Do: You can change the settings. | Use: | ||||||
| - Do not: Note that you can change the settings. | 
 | ||||||
|  | - You can change the settings. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - Note that you can change the settings. | ||||||
| 
 | 
 | ||||||
| ## on | ## on | ||||||
| 
 | 
 | ||||||
| When documenting how to select high-level UI elements, use the word **on**. | When documenting how to select high-level UI elements, use the word **on**. | ||||||
| 
 | 
 | ||||||
| - Do: `On the left sidebar...` | Use: | ||||||
|  | 
 | ||||||
|  | - `On the left sidebar...` | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
| - Do not: `From the left sidebar...` or `In the left sidebar...` | - Do not: `From the left sidebar...` or `In the left sidebar...` | ||||||
| 
 | 
 | ||||||
| ## once | ## once | ||||||
| 
 | 
 | ||||||
| The word **once** means **one time**. Don't use it to mean **after** or **when**. | The word **once** means **one time**. Don't use it to mean **after** or **when**. | ||||||
| 
 | 
 | ||||||
| - Do: When the process is complete... | Use: | ||||||
| - Do not: Once the process is complete... | 
 | ||||||
|  | - When the process is complete... | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - Once the process is complete... | ||||||
| 
 | 
 | ||||||
| ## only | ## only | ||||||
| 
 | 
 | ||||||
|  | @ -566,8 +643,8 @@ When writing about the Reporter role: | ||||||
| - Do not use the phrase, **if you are a reporter** to mean someone who is assigned the Reporter | - Do not use the phrase, **if you are a reporter** to mean someone who is assigned the Reporter | ||||||
|   role. Instead, write it out. For example, **if you are assigned the Reporter role**. |   role. Instead, write it out. For example, **if you are assigned the Reporter role**. | ||||||
| - To describe a situation where the Reporter role is the minimum required: | - To describe a situation where the Reporter role is the minimum required: | ||||||
|   - Do: at least the Reporter role |   - Use: at least the Reporter role | ||||||
|   - Do not: the Reporter role or higher |   - Instead of: the Reporter role or higher | ||||||
| 
 | 
 | ||||||
| Do not use **Reporter permissions**. A user who is assigned the Reporter role has a set of associated permissions. | Do not use **Reporter permissions**. A user who is assigned the Reporter role has a set of associated permissions. | ||||||
| 
 | 
 | ||||||
|  | @ -589,8 +666,13 @@ Use lowercase for **runners**. These are the agents that run CI/CD jobs. See als | ||||||
| 
 | 
 | ||||||
| Do not use **(s)** to make a word optionally plural. It can slow down comprehension. For example: | Do not use **(s)** to make a word optionally plural. It can slow down comprehension. For example: | ||||||
| 
 | 
 | ||||||
| - Do: Select the jobs you want. | Use: | ||||||
| - Do not: Select the job(s) you want. | 
 | ||||||
|  | - Select the jobs you want. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - Select the job(s) you want. | ||||||
| 
 | 
 | ||||||
| If you can select multiples of something, then write the word as plural. | If you can select multiples of something, then write the word as plural. | ||||||
| 
 | 
 | ||||||
|  | @ -612,7 +694,12 @@ into separate areas, refer to these areas as sections. | ||||||
| We often think of expandable/collapsible areas as **sections**. When you refer to expanding | We often think of expandable/collapsible areas as **sections**. When you refer to expanding | ||||||
| or collapsing a section, don't include the word **section**. | or collapsing a section, don't include the word **section**. | ||||||
| 
 | 
 | ||||||
| - Do: Expand **Auto DevOps**. | Use: | ||||||
|  | 
 | ||||||
|  | - Expand **Auto DevOps**. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
| - Do not: Expand the **Auto DevOps** section. | - Do not: Expand the **Auto DevOps** section. | ||||||
| 
 | 
 | ||||||
| ## select | ## select | ||||||
|  | @ -645,8 +732,13 @@ Do not use **simply** or **simple**. If the user doesn't find the process to be | ||||||
| 
 | 
 | ||||||
| The word **since** indicates a timeframe. For example, **Since 1984, Bon Jovi has existed**. Don't use **since** to mean **because**. | The word **since** indicates a timeframe. For example, **Since 1984, Bon Jovi has existed**. Don't use **since** to mean **because**. | ||||||
| 
 | 
 | ||||||
| - Do: Because you have the Developer role, you can delete the widget. | Use: | ||||||
| - Do not: Since you have the Developer role, you can delete the widget. | 
 | ||||||
|  | - Because you have the Developer role, you can delete the widget. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - Since you have the Developer role, you can delete the widget. | ||||||
| 
 | 
 | ||||||
| ## slashes | ## slashes | ||||||
| 
 | 
 | ||||||
|  | @ -664,8 +756,13 @@ Use **subgroup** (no hyphen) instead of **sub-group**. ([Vale](../testing.md#val | ||||||
| 
 | 
 | ||||||
| Do not use **that** when describing a noun. For example: | Do not use **that** when describing a noun. For example: | ||||||
| 
 | 
 | ||||||
| - Do: The file you save... | Use: | ||||||
| - Do not: The file **that** you save... | 
 | ||||||
|  | - The file you save... | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - The file **that** you save... | ||||||
| 
 | 
 | ||||||
| See also [this, these, that, those](#this-these-that-those). | See also [this, these, that, those](#this-these-that-those). | ||||||
| 
 | 
 | ||||||
|  | @ -684,8 +781,13 @@ Use **text box** instead of **field** or **box** when referring to the UI elemen | ||||||
| 
 | 
 | ||||||
| Try to avoid **there is** and **there are**. These phrases hide the subject. | Try to avoid **there is** and **there are**. These phrases hide the subject. | ||||||
| 
 | 
 | ||||||
| - Do: The bucket has holes. | Use: | ||||||
| - Do not: There are holes in the bucket. | 
 | ||||||
|  | - The bucket has holes. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - There are holes in the bucket. | ||||||
| 
 | 
 | ||||||
| ## they | ## they | ||||||
| 
 | 
 | ||||||
|  | @ -697,17 +799,17 @@ a gender-neutral pronoun. | ||||||
| 
 | 
 | ||||||
| Always follow these words with a noun. For example: | Always follow these words with a noun. For example: | ||||||
| 
 | 
 | ||||||
| - Do: **This setting** improves performance. | - Use: **This setting** improves performance. | ||||||
| - Do not: **This** improves performance. | - Instead of: **This** improves performance. | ||||||
| 
 | 
 | ||||||
| - Do: **These pants** are the best. | - Use: **These pants** are the best. | ||||||
| - Do not: **These** are the best. | - Instead of: **These** are the best. | ||||||
| 
 | 
 | ||||||
| - Do: **That droid** is the one you are looking for. | - Use: **That droid** is the one you are looking for. | ||||||
| - Do not: **That** is the one you are looking for. | - Instead of: **That** is the one you are looking for. | ||||||
| 
 | 
 | ||||||
| - Do: **Those settings** need to be configured. (Or even better, **Configure those settings.**) | - Use: **Those settings** need to be configured. (Or even better, **Configure those settings.**) | ||||||
| - Do not: **Those** need to be configured. | - Instead of: **Those** need to be configured. | ||||||
| 
 | 
 | ||||||
| ## to-do item | ## to-do item | ||||||
| 
 | 
 | ||||||
|  | @ -736,8 +838,13 @@ Do not use **useful**. If the user doesn't find the process to be useful, we los | ||||||
| When possible, address the reader directly, instead of calling them **users**. | When possible, address the reader directly, instead of calling them **users**. | ||||||
| Use the [second person](#you-your-yours), **you**, instead. | Use the [second person](#you-your-yours), **you**, instead. | ||||||
| 
 | 
 | ||||||
| - Do: You can configure a pipeline. | Use: | ||||||
| - Do not: Users can configure a pipeline. | 
 | ||||||
|  | - You can configure a pipeline. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - Users can configure a pipeline. | ||||||
| 
 | 
 | ||||||
| ## utilize | ## utilize | ||||||
| 
 | 
 | ||||||
|  | @ -756,8 +863,13 @@ Do not use Latin abbreviations. Use **with**, **through**, or **by using** inste | ||||||
| 
 | 
 | ||||||
| Try to avoid **we** and focus instead on how the user can accomplish something in GitLab. | Try to avoid **we** and focus instead on how the user can accomplish something in GitLab. | ||||||
| 
 | 
 | ||||||
| - Do: Use widgets when you have work you want to organize. | Use: | ||||||
| - Do not: We created a feature for you to add widgets. | 
 | ||||||
|  | - Use widgets when you have work you want to organize. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - We created a feature for you to add widgets. | ||||||
| 
 | 
 | ||||||
| One exception: You can use **we recommend** instead of **it is recommended** or **GitLab recommends**. ([Vale](../testing.md#vale) rule: [`SubstitutionSuggestions.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/SubstitutionSuggestions.yml)) | One exception: You can use **we recommend** instead of **it is recommended** or **GitLab recommends**. ([Vale](../testing.md#vale) rule: [`SubstitutionSuggestions.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/.vale/gitlab/SubstitutionSuggestions.yml)) | ||||||
| 
 | 
 | ||||||
|  | @ -770,8 +882,13 @@ Do not use **whitelist**. Another option is **allowlist**. ([Vale](../testing.md | ||||||
| Use **you**, **your**, and **yours** instead of [**the user** and **the user's**](#user-users). | Use **you**, **your**, and **yours** instead of [**the user** and **the user's**](#user-users). | ||||||
| Documentation should be from the [point of view](https://design.gitlab.com/content/voice-tone#point-of-view) of the reader. | Documentation should be from the [point of view](https://design.gitlab.com/content/voice-tone#point-of-view) of the reader. | ||||||
| 
 | 
 | ||||||
| - Do: You can configure a pipeline. | Use: | ||||||
| - Do not: Users can configure a pipeline. | 
 | ||||||
|  | - You can configure a pipeline. | ||||||
|  | 
 | ||||||
|  | Instead of: | ||||||
|  | 
 | ||||||
|  | - Users can configure a pipeline. | ||||||
| 
 | 
 | ||||||
| <!-- vale on --> | <!-- vale on --> | ||||||
| <!-- markdownlint-enable --> | <!-- markdownlint-enable --> | ||||||
|  |  | ||||||
|  | @ -492,10 +492,13 @@ image::screenshot.png[block image,800,450] | ||||||
| Press image:reload.svg[reload,16,opts=interactive] to reload the page. | Press image:reload.svg[reload,16,opts=interactive] to reload the page. | ||||||
| 
 | 
 | ||||||
| video::movie.mp4[width=640,start=60,end=140,options=autoplay] | video::movie.mp4[width=640,start=60,end=140,options=autoplay] | ||||||
|  | ``` | ||||||
| 
 | 
 | ||||||
| video::aHjpOzsQ9YI[youtube] | GitLab does not support embedding YouTube and Vimeo videos in AsciiDoc content. | ||||||
|  | Use a standard AsciiDoc link: | ||||||
| 
 | 
 | ||||||
| video::300817511[vimeo] | ```plaintext | ||||||
|  | https://www.youtube.com/watch?v=BlaZ65-b7y0[Link text for the video] | ||||||
| ``` | ``` | ||||||
| 
 | 
 | ||||||
| ### Breaks | ### Breaks | ||||||
|  |  | ||||||
|  | @ -344,6 +344,11 @@ Here is an example of milestones with no releases, one release, and two releases | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
|  | NOTE: | ||||||
|  | A subgroup's project releases cannot be associated with a supergroup's milestone. To learn | ||||||
|  | more, read issue #328054, | ||||||
|  | [Releases cannot be associated with a supergroup milestone](https://gitlab.com/gitlab-org/gitlab/-/issues/328054). | ||||||
|  | 
 | ||||||
| ## Get notified when a release is created | ## Get notified when a release is created | ||||||
| 
 | 
 | ||||||
| > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26001) in GitLab 12.4. | > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26001) in GitLab 12.4. | ||||||
|  |  | ||||||
|  | @ -143,7 +143,9 @@ you can choose from: | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
| 
 | 
 | ||||||
| When filtering by a deploy date, you must enter the date manually. Deploy dates | When filtering by `Deployed-before` or `Deployed-after`, the date refers to when  | ||||||
|  | the deployment to an environment (triggered by the merge commit) completed successfully. | ||||||
|  | You must enter the deploy date manually. Deploy dates | ||||||
| use the format `YYYY-MM-DD`, and must be quoted if you wish to specify | use the format `YYYY-MM-DD`, and must be quoted if you wish to specify | ||||||
| both a date and time (`"YYYY-MM-DD HH:MM"`): | both a date and time (`"YYYY-MM-DD HH:MM"`): | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -14,11 +14,11 @@ module Gitlab | ||||||
|           @result_dir = result_dir |           @result_dir = result_dir | ||||||
|         end |         end | ||||||
| 
 | 
 | ||||||
|         def observe(version:, name:, &block) |         def observe(version:, name:, connection:, &block) | ||||||
|           observation = Observation.new(version, name) |           observation = Observation.new(version, name) | ||||||
|           observation.success = true |           observation.success = true | ||||||
| 
 | 
 | ||||||
|           observers = observer_classes.map { |c| c.new(observation, @result_dir) } |           observers = observer_classes.map { |c| c.new(observation, @result_dir, connection) } | ||||||
| 
 | 
 | ||||||
|           exception = nil |           exception = nil | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,8 +7,8 @@ module Gitlab | ||||||
|         class MigrationObserver |         class MigrationObserver | ||||||
|           attr_reader :connection, :observation, :output_dir |           attr_reader :connection, :observation, :output_dir | ||||||
| 
 | 
 | ||||||
|           def initialize(observation, output_dir) |           def initialize(observation, output_dir, connection) | ||||||
|             @connection = ActiveRecord::Base.connection |             @connection = connection | ||||||
|             @observation = observation |             @observation = observation | ||||||
|             @output_dir = output_dir |             @output_dir = output_dir | ||||||
|           end |           end | ||||||
|  |  | ||||||
|  | @ -69,7 +69,7 @@ module Gitlab | ||||||
|           instrumentation = Instrumentation.new(result_dir: result_dir) |           instrumentation = Instrumentation.new(result_dir: result_dir) | ||||||
| 
 | 
 | ||||||
|           sorted_migrations.each do |migration| |           sorted_migrations.each do |migration| | ||||||
|             instrumentation.observe(version: migration.version, name: migration.name) do |             instrumentation.observe(version: migration.version, name: migration.name, connection: ActiveRecord::Migration.connection) do | ||||||
|               ActiveRecord::Migrator.new(direction, migration_context.migrations, migration_context.schema_migration, migration.version).run |               ActiveRecord::Migrator.new(direction, migration_context.migrations, migration_context.schema_migration, migration.version).run | ||||||
|             end |             end | ||||||
|           end |           end | ||||||
|  |  | ||||||
|  | @ -13,6 +13,10 @@ module Gitlab | ||||||
|       'https://staging.gitlab.com' |       'https://staging.gitlab.com' | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|  |     def self.canary_toggle_com_url | ||||||
|  |       'https://next.gitlab.com' | ||||||
|  |     end | ||||||
|  | 
 | ||||||
|     def self.subdomain_regex |     def self.subdomain_regex | ||||||
|       %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}.freeze |       %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}.freeze | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -16406,6 +16406,9 @@ msgstr "" | ||||||
| msgid "Go to next page" | msgid "Go to next page" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  | msgid "Go to page %{page}" | ||||||
|  | msgstr "" | ||||||
|  | 
 | ||||||
| msgid "Go to parent" | msgid "Go to parent" | ||||||
| msgstr "" | msgstr "" | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -5,6 +5,7 @@ import environmentToRollback from '~/environments/graphql/queries/environment_to | ||||||
| import environmentToDelete from '~/environments/graphql/queries/environment_to_delete.query.graphql'; | import environmentToDelete from '~/environments/graphql/queries/environment_to_delete.query.graphql'; | ||||||
| import createMockApollo from 'helpers/mock_apollo_helper'; | import createMockApollo from 'helpers/mock_apollo_helper'; | ||||||
| import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.query.graphql'; | import pollIntervalQuery from '~/environments/graphql/queries/poll_interval.query.graphql'; | ||||||
|  | import pageInfoQuery from '~/environments/graphql/queries/page_info.query.graphql'; | ||||||
| import { TEST_HOST } from 'helpers/test_constants'; | import { TEST_HOST } from 'helpers/test_constants'; | ||||||
| import { | import { | ||||||
|   environmentsApp, |   environmentsApp, | ||||||
|  | @ -37,9 +38,11 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|     it('should fetch environments and map them to frontend data', async () => { |     it('should fetch environments and map them to frontend data', async () => { | ||||||
|       const cache = { writeQuery: jest.fn() }; |       const cache = { writeQuery: jest.fn() }; | ||||||
|       const scope = 'available'; |       const scope = 'available'; | ||||||
|       mock.onGet(ENDPOINT, { params: { nested: true, scope } }).reply(200, environmentsApp, {}); |       mock | ||||||
|  |         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1 } }) | ||||||
|  |         .reply(200, environmentsApp, {}); | ||||||
| 
 | 
 | ||||||
|       const app = await mockResolvers.Query.environmentApp(null, { scope }, { cache }); |       const app = await mockResolvers.Query.environmentApp(null, { scope, page: 1 }, { cache }); | ||||||
|       expect(app).toEqual(resolvedEnvironmentsApp); |       expect(app).toEqual(resolvedEnvironmentsApp); | ||||||
|       expect(cache.writeQuery).toHaveBeenCalledWith({ |       expect(cache.writeQuery).toHaveBeenCalledWith({ | ||||||
|         query: pollIntervalQuery, |         query: pollIntervalQuery, | ||||||
|  | @ -49,14 +52,70 @@ describe('~/frontend/environments/graphql/resolvers', () => { | ||||||
|     it('should set the poll interval when there is one', async () => { |     it('should set the poll interval when there is one', async () => { | ||||||
|       const cache = { writeQuery: jest.fn() }; |       const cache = { writeQuery: jest.fn() }; | ||||||
|       const scope = 'stopped'; |       const scope = 'stopped'; | ||||||
|  |       const interval = 3000; | ||||||
|       mock |       mock | ||||||
|         .onGet(ENDPOINT, { params: { nested: true, scope } }) |         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1 } }) | ||||||
|         .reply(200, environmentsApp, { 'poll-interval': 3000 }); |         .reply(200, environmentsApp, { | ||||||
|  |           'poll-interval': interval, | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|       await mockResolvers.Query.environmentApp(null, { scope }, { cache }); |       await mockResolvers.Query.environmentApp(null, { scope, page: 1 }, { cache }); | ||||||
|       expect(cache.writeQuery).toHaveBeenCalledWith({ |       expect(cache.writeQuery).toHaveBeenCalledWith({ | ||||||
|         query: pollIntervalQuery, |         query: pollIntervalQuery, | ||||||
|         data: { interval: 3000 }, |         data: { interval }, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |     it('should set page info if there is any', async () => { | ||||||
|  |       const cache = { writeQuery: jest.fn() }; | ||||||
|  |       const scope = 'stopped'; | ||||||
|  |       mock | ||||||
|  |         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1 } }) | ||||||
|  |         .reply(200, environmentsApp, { | ||||||
|  |           'x-next-page': '2', | ||||||
|  |           'x-page': '1', | ||||||
|  |           'X-Per-Page': '2', | ||||||
|  |           'X-Prev-Page': '', | ||||||
|  |           'X-TOTAL': '37', | ||||||
|  |           'X-Total-Pages': '5', | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |       await mockResolvers.Query.environmentApp(null, { scope, page: 1 }, { cache }); | ||||||
|  |       expect(cache.writeQuery).toHaveBeenCalledWith({ | ||||||
|  |         query: pageInfoQuery, | ||||||
|  |         data: { | ||||||
|  |           pageInfo: { | ||||||
|  |             total: 37, | ||||||
|  |             perPage: 2, | ||||||
|  |             previousPage: NaN, | ||||||
|  |             totalPages: 5, | ||||||
|  |             nextPage: 2, | ||||||
|  |             page: 1, | ||||||
|  |             __typename: 'LocalPageInfo', | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |     }); | ||||||
|  |     it('should not set page info if there is none', async () => { | ||||||
|  |       const cache = { writeQuery: jest.fn() }; | ||||||
|  |       const scope = 'stopped'; | ||||||
|  |       mock | ||||||
|  |         .onGet(ENDPOINT, { params: { nested: true, scope, page: 1 } }) | ||||||
|  |         .reply(200, environmentsApp, {}); | ||||||
|  | 
 | ||||||
|  |       await mockResolvers.Query.environmentApp(null, { scope, page: 1 }, { cache }); | ||||||
|  |       expect(cache.writeQuery).toHaveBeenCalledWith({ | ||||||
|  |         query: pageInfoQuery, | ||||||
|  |         data: { | ||||||
|  |           pageInfo: { | ||||||
|  |             __typename: 'LocalPageInfo', | ||||||
|  |             nextPage: NaN, | ||||||
|  |             page: NaN, | ||||||
|  |             perPage: NaN, | ||||||
|  |             previousPage: NaN, | ||||||
|  |             total: NaN, | ||||||
|  |             totalPages: NaN, | ||||||
|  |           }, | ||||||
|  |         }, | ||||||
|       }); |       }); | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  | @ -1,9 +1,11 @@ | ||||||
| import Vue, { nextTick } from 'vue'; | import Vue, { nextTick } from 'vue'; | ||||||
| import VueApollo from 'vue-apollo'; | import VueApollo from 'vue-apollo'; | ||||||
|  | import { GlPagination } from '@gitlab/ui'; | ||||||
| import { mountExtended } from 'helpers/vue_test_utils_helper'; | import { mountExtended } from 'helpers/vue_test_utils_helper'; | ||||||
| import createMockApollo from 'helpers/mock_apollo_helper'; | import createMockApollo from 'helpers/mock_apollo_helper'; | ||||||
| import waitForPromises from 'helpers/wait_for_promises'; | import waitForPromises from 'helpers/wait_for_promises'; | ||||||
| import { __, s__ } from '~/locale'; | import setWindowLocation from 'helpers/set_window_location_helper'; | ||||||
|  | import { sprintf, __, s__ } from '~/locale'; | ||||||
| import EnvironmentsApp from '~/environments/components/new_environments_app.vue'; | import EnvironmentsApp from '~/environments/components/new_environments_app.vue'; | ||||||
| import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue'; | import EnvironmentsFolder from '~/environments/components/new_environment_folder.vue'; | ||||||
| import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data'; | import { resolvedEnvironmentsApp, resolvedFolder } from './graphql/mock_data'; | ||||||
|  | @ -14,12 +16,14 @@ describe('~/environments/components/new_environments_app.vue', () => { | ||||||
|   let wrapper; |   let wrapper; | ||||||
|   let environmentAppMock; |   let environmentAppMock; | ||||||
|   let environmentFolderMock; |   let environmentFolderMock; | ||||||
|  |   let paginationMock; | ||||||
| 
 | 
 | ||||||
|   const createApolloProvider = () => { |   const createApolloProvider = () => { | ||||||
|     const mockResolvers = { |     const mockResolvers = { | ||||||
|       Query: { |       Query: { | ||||||
|         environmentApp: environmentAppMock, |         environmentApp: environmentAppMock, | ||||||
|         folder: environmentFolderMock, |         folder: environmentFolderMock, | ||||||
|  |         pageInfo: paginationMock, | ||||||
|       }, |       }, | ||||||
|     }; |     }; | ||||||
| 
 | 
 | ||||||
|  | @ -37,9 +41,23 @@ describe('~/environments/components/new_environments_app.vue', () => { | ||||||
|       apolloProvider, |       apolloProvider, | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|   const createWrapperWithMocked = async ({ provide = {}, environmentsApp, folder }) => { |   const createWrapperWithMocked = async ({ | ||||||
|  |     provide = {}, | ||||||
|  |     environmentsApp, | ||||||
|  |     folder, | ||||||
|  |     pageInfo = { | ||||||
|  |       total: 20, | ||||||
|  |       perPage: 5, | ||||||
|  |       nextPage: 3, | ||||||
|  |       page: 2, | ||||||
|  |       previousPage: 1, | ||||||
|  |       __typename: 'LocalPageInfo', | ||||||
|  |     }, | ||||||
|  |   }) => { | ||||||
|  |     setWindowLocation('?scope=available&page=2'); | ||||||
|     environmentAppMock.mockReturnValue(environmentsApp); |     environmentAppMock.mockReturnValue(environmentsApp); | ||||||
|     environmentFolderMock.mockReturnValue(folder); |     environmentFolderMock.mockReturnValue(folder); | ||||||
|  |     paginationMock.mockReturnValue(pageInfo); | ||||||
|     const apolloProvider = createApolloProvider(); |     const apolloProvider = createApolloProvider(); | ||||||
|     wrapper = createWrapper({ apolloProvider, provide }); |     wrapper = createWrapper({ apolloProvider, provide }); | ||||||
| 
 | 
 | ||||||
|  | @ -50,6 +68,7 @@ describe('~/environments/components/new_environments_app.vue', () => { | ||||||
|   beforeEach(() => { |   beforeEach(() => { | ||||||
|     environmentAppMock = jest.fn(); |     environmentAppMock = jest.fn(); | ||||||
|     environmentFolderMock = jest.fn(); |     environmentFolderMock = jest.fn(); | ||||||
|  |     paginationMock = jest.fn(); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   afterEach(() => { |   afterEach(() => { | ||||||
|  | @ -118,42 +137,135 @@ describe('~/environments/components/new_environments_app.vue', () => { | ||||||
|     expect(button.exists()).toBe(false); |     expect(button.exists()).toBe(false); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('should show tabs for available and stopped environmets', async () => { |   describe('tabs', () => { | ||||||
|     await createWrapperWithMocked({ |     it('should show tabs for available and stopped environmets', async () => { | ||||||
|       environmentsApp: resolvedEnvironmentsApp, |       await createWrapperWithMocked({ | ||||||
|       folder: resolvedFolder, |         environmentsApp: resolvedEnvironmentsApp, | ||||||
|  |         folder: resolvedFolder, | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       const [available, stopped] = wrapper.findAllByRole('tab').wrappers; | ||||||
|  | 
 | ||||||
|  |       expect(available.text()).toContain(__('Available')); | ||||||
|  |       expect(available.text()).toContain(resolvedEnvironmentsApp.availableCount); | ||||||
|  |       expect(stopped.text()).toContain(__('Stopped')); | ||||||
|  |       expect(stopped.text()).toContain(resolvedEnvironmentsApp.stoppedCount); | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     const [available, stopped] = wrapper.findAllByRole('tab').wrappers; |     it('should change the requested scope on tab change', async () => { | ||||||
|  |       await createWrapperWithMocked({ | ||||||
|  |         environmentsApp: resolvedEnvironmentsApp, | ||||||
|  |         folder: resolvedFolder, | ||||||
|  |       }); | ||||||
|  |       const stopped = wrapper.findByRole('tab', { | ||||||
|  |         name: `${__('Stopped')} ${resolvedEnvironmentsApp.stoppedCount}`, | ||||||
|  |       }); | ||||||
| 
 | 
 | ||||||
|     expect(available.text()).toContain(__('Available')); |       stopped.trigger('click'); | ||||||
|     expect(available.text()).toContain(resolvedEnvironmentsApp.availableCount); | 
 | ||||||
|     expect(stopped.text()).toContain(__('Stopped')); |       await nextTick(); | ||||||
|     expect(stopped.text()).toContain(resolvedEnvironmentsApp.stoppedCount); |       await waitForPromises(); | ||||||
|  | 
 | ||||||
|  |       expect(environmentAppMock).toHaveBeenCalledWith( | ||||||
|  |         expect.anything(), | ||||||
|  |         expect.objectContaining({ scope: 'stopped' }), | ||||||
|  |         expect.anything(), | ||||||
|  |         expect.anything(), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('should change the requested scope on tab change', async () => { |   describe('pagination', () => { | ||||||
|     environmentAppMock.mockReturnValue(resolvedEnvironmentsApp); |     it('should sync page from query params on load', async () => { | ||||||
|     environmentFolderMock.mockReturnValue(resolvedFolder); |       await createWrapperWithMocked({ | ||||||
|     const apolloProvider = createApolloProvider(); |         environmentsApp: resolvedEnvironmentsApp, | ||||||
|     wrapper = createWrapper({ apolloProvider }); |         folder: resolvedFolder, | ||||||
|  |       }); | ||||||
| 
 | 
 | ||||||
|     await waitForPromises(); |       expect(wrapper.findComponent(GlPagination).props('value')).toBe(2); | ||||||
|     await nextTick(); |  | ||||||
|     const stopped = wrapper.findByRole('tab', { |  | ||||||
|       name: `${__('Stopped')} ${resolvedEnvironmentsApp.stoppedCount}`, |  | ||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     stopped.trigger('click'); |     it('should change the requested page on next page click', async () => { | ||||||
|  |       await createWrapperWithMocked({ | ||||||
|  |         environmentsApp: resolvedEnvironmentsApp, | ||||||
|  |         folder: resolvedFolder, | ||||||
|  |       }); | ||||||
|  |       const next = wrapper.findByRole('link', { | ||||||
|  |         name: __('Go to next page'), | ||||||
|  |       }); | ||||||
| 
 | 
 | ||||||
|     await nextTick(); |       next.trigger('click'); | ||||||
|     await waitForPromises(); |  | ||||||
| 
 | 
 | ||||||
|     expect(environmentAppMock).toHaveBeenCalledWith( |       await nextTick(); | ||||||
|       expect.anything(), |       await waitForPromises(); | ||||||
|       { scope: 'stopped' }, | 
 | ||||||
|       expect.anything(), |       expect(environmentAppMock).toHaveBeenCalledWith( | ||||||
|       expect.anything(), |         expect.anything(), | ||||||
|     ); |         expect.objectContaining({ page: 3 }), | ||||||
|  |         expect.anything(), | ||||||
|  |         expect.anything(), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should change the requested page on previous page click', async () => { | ||||||
|  |       await createWrapperWithMocked({ | ||||||
|  |         environmentsApp: resolvedEnvironmentsApp, | ||||||
|  |         folder: resolvedFolder, | ||||||
|  |       }); | ||||||
|  |       const prev = wrapper.findByRole('link', { | ||||||
|  |         name: __('Go to previous page'), | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       prev.trigger('click'); | ||||||
|  | 
 | ||||||
|  |       await nextTick(); | ||||||
|  |       await waitForPromises(); | ||||||
|  | 
 | ||||||
|  |       expect(environmentAppMock).toHaveBeenCalledWith( | ||||||
|  |         expect.anything(), | ||||||
|  |         expect.objectContaining({ page: 1 }), | ||||||
|  |         expect.anything(), | ||||||
|  |         expect.anything(), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should change the requested page on specific page click', async () => { | ||||||
|  |       await createWrapperWithMocked({ | ||||||
|  |         environmentsApp: resolvedEnvironmentsApp, | ||||||
|  |         folder: resolvedFolder, | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       const page = 1; | ||||||
|  |       const pageButton = wrapper.findByRole('link', { | ||||||
|  |         name: sprintf(__('Go to page %{page}'), { page }), | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       pageButton.trigger('click'); | ||||||
|  | 
 | ||||||
|  |       await nextTick(); | ||||||
|  |       await waitForPromises(); | ||||||
|  | 
 | ||||||
|  |       expect(environmentAppMock).toHaveBeenCalledWith( | ||||||
|  |         expect.anything(), | ||||||
|  |         expect.objectContaining({ page }), | ||||||
|  |         expect.anything(), | ||||||
|  |         expect.anything(), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     it('should sync the query params to the new page', async () => { | ||||||
|  |       await createWrapperWithMocked({ | ||||||
|  |         environmentsApp: resolvedEnvironmentsApp, | ||||||
|  |         folder: resolvedFolder, | ||||||
|  |       }); | ||||||
|  |       const next = wrapper.findByRole('link', { | ||||||
|  |         name: __('Go to next page'), | ||||||
|  |       }); | ||||||
|  | 
 | ||||||
|  |       next.trigger('click'); | ||||||
|  | 
 | ||||||
|  |       await nextTick(); | ||||||
|  |       expect(window.location.search).toBe('?scope=available&page=3'); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | @ -3,6 +3,7 @@ require 'spec_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe Gitlab::Database::Migrations::Instrumentation do | RSpec.describe Gitlab::Database::Migrations::Instrumentation do | ||||||
|   let(:result_dir) { Dir.mktmpdir } |   let(:result_dir) { Dir.mktmpdir } | ||||||
|  |   let(:connection) { ActiveRecord::Migration.connection } | ||||||
| 
 | 
 | ||||||
|   after do |   after do | ||||||
|     FileUtils.rm_rf(result_dir) |     FileUtils.rm_rf(result_dir) | ||||||
|  | @ -14,11 +15,11 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do | ||||||
|     let(:migration_version) { '12345' } |     let(:migration_version) { '12345' } | ||||||
| 
 | 
 | ||||||
|     it 'executes the given block' do |     it 'executes the given block' do | ||||||
|       expect { |b| subject.observe(version: migration_version, name: migration_name, &b) }.to yield_control |       expect { |b| subject.observe(version: migration_version, name: migration_name, connection: connection, &b) }.to yield_control | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     context 'behavior with observers' do |     context 'behavior with observers' do | ||||||
|       subject { described_class.new(observer_classes: [Gitlab::Database::Migrations::Observers::MigrationObserver], result_dir: result_dir).observe(version: migration_version, name: migration_name) {} } |       subject { described_class.new(observer_classes: [Gitlab::Database::Migrations::Observers::MigrationObserver], result_dir: result_dir).observe(version: migration_version, name: migration_name, connection: connection) {} } | ||||||
| 
 | 
 | ||||||
|       let(:observer) { instance_double('Gitlab::Database::Migrations::Observers::MigrationObserver', before: nil, after: nil, record: nil) } |       let(:observer) { instance_double('Gitlab::Database::Migrations::Observers::MigrationObserver', before: nil, after: nil, record: nil) } | ||||||
| 
 | 
 | ||||||
|  | @ -29,7 +30,7 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do | ||||||
|       it 'instantiates observer with observation' do |       it 'instantiates observer with observation' do | ||||||
|         expect(Gitlab::Database::Migrations::Observers::MigrationObserver) |         expect(Gitlab::Database::Migrations::Observers::MigrationObserver) | ||||||
|           .to receive(:new) |           .to receive(:new) | ||||||
|           .with(instance_of(Gitlab::Database::Migrations::Observation), anything) { |observation| expect(observation.version).to eq(migration_version) } |           .with(instance_of(Gitlab::Database::Migrations::Observation), anything, connection) { |observation| expect(observation.version).to eq(migration_version) } | ||||||
|           .and_return(observer) |           .and_return(observer) | ||||||
| 
 | 
 | ||||||
|         subject |         subject | ||||||
|  | @ -63,7 +64,7 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     context 'on successful execution' do |     context 'on successful execution' do | ||||||
|       subject { described_class.new(result_dir: result_dir).observe(version: migration_version, name: migration_name) {} } |       subject { described_class.new(result_dir: result_dir).observe(version: migration_version, name: migration_name, connection: connection) {} } | ||||||
| 
 | 
 | ||||||
|       it 'records walltime' do |       it 'records walltime' do | ||||||
|         expect(subject.walltime).not_to be_nil |         expect(subject.walltime).not_to be_nil | ||||||
|  | @ -83,7 +84,7 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do | ||||||
|     end |     end | ||||||
| 
 | 
 | ||||||
|     context 'upon failure' do |     context 'upon failure' do | ||||||
|       subject { described_class.new(result_dir: result_dir).observe(version: migration_version, name: migration_name) { raise 'something went wrong' } } |       subject { described_class.new(result_dir: result_dir).observe(version: migration_version, name: migration_name, connection: connection) { raise 'something went wrong' } } | ||||||
| 
 | 
 | ||||||
|       it 'raises the exception' do |       it 'raises the exception' do | ||||||
|         expect { subject }.to raise_error(/something went wrong/) |         expect { subject }.to raise_error(/something went wrong/) | ||||||
|  | @ -93,7 +94,7 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do | ||||||
|         subject { instance.observations.first } |         subject { instance.observations.first } | ||||||
| 
 | 
 | ||||||
|         before do |         before do | ||||||
|           instance.observe(version: migration_version, name: migration_name) { raise 'something went wrong' } |           instance.observe(version: migration_version, name: migration_name, connection: connection) { raise 'something went wrong' } | ||||||
|         rescue StandardError |         rescue StandardError | ||||||
|           # ignore |           # ignore | ||||||
|         end |         end | ||||||
|  | @ -125,8 +126,8 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do | ||||||
|       let(:migration2) { double('migration2', call: nil) } |       let(:migration2) { double('migration2', call: nil) } | ||||||
| 
 | 
 | ||||||
|       it 'records observations for all migrations' do |       it 'records observations for all migrations' do | ||||||
|         subject.observe(version: migration_version, name: migration_name) {} |         subject.observe(version: migration_version, name: migration_name, connection: connection) {} | ||||||
|         subject.observe(version: migration_version, name: migration_name) { raise 'something went wrong' } rescue nil |         subject.observe(version: migration_version, name: migration_name, connection: connection) { raise 'something went wrong' } rescue nil | ||||||
| 
 | 
 | ||||||
|         expect(subject.observations.size).to eq(2) |         expect(subject.observations.size).to eq(2) | ||||||
|       end |       end | ||||||
|  |  | ||||||
|  | @ -2,10 +2,10 @@ | ||||||
| require 'spec_helper' | require 'spec_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe Gitlab::Database::Migrations::Observers::QueryDetails do | RSpec.describe Gitlab::Database::Migrations::Observers::QueryDetails do | ||||||
|   subject { described_class.new(observation, directory_path) } |   subject { described_class.new(observation, directory_path, connection) } | ||||||
| 
 | 
 | ||||||
|  |   let(:connection) { ActiveRecord::Migration.connection } | ||||||
|   let(:observation) { Gitlab::Database::Migrations::Observation.new(migration_version, migration_name) } |   let(:observation) { Gitlab::Database::Migrations::Observation.new(migration_version, migration_name) } | ||||||
|   let(:connection) { ActiveRecord::Base.connection } |  | ||||||
|   let(:query) { "select date_trunc('day', $1::timestamptz) + $2 * (interval '1 hour')" } |   let(:query) { "select date_trunc('day', $1::timestamptz) + $2 * (interval '1 hour')" } | ||||||
|   let(:query_binds) { [Time.current, 3] } |   let(:query_binds) { [Time.current, 3] } | ||||||
|   let(:directory_path) { Dir.mktmpdir } |   let(:directory_path) { Dir.mktmpdir } | ||||||
|  |  | ||||||
|  | @ -2,10 +2,10 @@ | ||||||
| require 'spec_helper' | require 'spec_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe Gitlab::Database::Migrations::Observers::QueryLog do | RSpec.describe Gitlab::Database::Migrations::Observers::QueryLog do | ||||||
|   subject { described_class.new(observation, directory_path) } |   subject { described_class.new(observation, directory_path, connection) } | ||||||
| 
 | 
 | ||||||
|   let(:observation) { Gitlab::Database::Migrations::Observation.new(migration_version, migration_name) } |   let(:observation) { Gitlab::Database::Migrations::Observation.new(migration_version, migration_name) } | ||||||
|   let(:connection) { ActiveRecord::Base.connection } |   let(:connection) { ActiveRecord::Migration.connection } | ||||||
|   let(:query) { 'select 1' } |   let(:query) { 'select 1' } | ||||||
|   let(:directory_path) { Dir.mktmpdir } |   let(:directory_path) { Dir.mktmpdir } | ||||||
|   let(:migration_version) { 20210422152437 } |   let(:migration_version) { 20210422152437 } | ||||||
|  |  | ||||||
|  | @ -2,10 +2,10 @@ | ||||||
| require 'spec_helper' | require 'spec_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe Gitlab::Database::Migrations::Observers::QueryStatistics do | RSpec.describe Gitlab::Database::Migrations::Observers::QueryStatistics do | ||||||
|   subject { described_class.new(observation, double("unused path")) } |   subject { described_class.new(observation, double("unused path"), connection) } | ||||||
| 
 | 
 | ||||||
|   let(:observation) { Gitlab::Database::Migrations::Observation.new } |   let(:observation) { Gitlab::Database::Migrations::Observation.new } | ||||||
|   let(:connection) { ActiveRecord::Base.connection } |   let(:connection) { ActiveRecord::Migration.connection } | ||||||
| 
 | 
 | ||||||
|   def mock_pgss(enabled: true) |   def mock_pgss(enabled: true) | ||||||
|     if enabled |     if enabled | ||||||
|  |  | ||||||
|  | @ -2,10 +2,10 @@ | ||||||
| require 'spec_helper' | require 'spec_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe Gitlab::Database::Migrations::Observers::TotalDatabaseSizeChange do | RSpec.describe Gitlab::Database::Migrations::Observers::TotalDatabaseSizeChange do | ||||||
|   subject { described_class.new(observation, double('unused path')) } |   subject { described_class.new(observation, double('unused path'), connection) } | ||||||
| 
 | 
 | ||||||
|   let(:observation) { Gitlab::Database::Migrations::Observation.new } |   let(:observation) { Gitlab::Database::Migrations::Observation.new } | ||||||
|   let(:connection) { ActiveRecord::Base.connection } |   let(:connection) { ActiveRecord::Migration.connection } | ||||||
|   let(:query) { 'select pg_database_size(current_database())' } |   let(:query) { 'select pg_database_size(current_database())' } | ||||||
| 
 | 
 | ||||||
|   it 'records the size change' do |   it 'records the size change' do | ||||||
|  |  | ||||||
|  | @ -2,8 +2,9 @@ | ||||||
| require 'spec_helper' | require 'spec_helper' | ||||||
| 
 | 
 | ||||||
| RSpec.describe Gitlab::Database::Migrations::Observers::TransactionDuration do | RSpec.describe Gitlab::Database::Migrations::Observers::TransactionDuration do | ||||||
|   subject(:transaction_duration_observer) { described_class.new(observation, directory_path) } |   subject(:transaction_duration_observer) { described_class.new(observation, directory_path, connection) } | ||||||
| 
 | 
 | ||||||
|  |   let(:connection) { ActiveRecord::Migration.connection } | ||||||
|   let(:observation) { Gitlab::Database::Migrations::Observation.new(migration_version, migration_name) } |   let(:observation) { Gitlab::Database::Migrations::Observation.new(migration_version, migration_name) } | ||||||
|   let(:directory_path) { Dir.mktmpdir } |   let(:directory_path) { Dir.mktmpdir } | ||||||
|   let(:log_file) { "#{directory_path}/#{migration_version}_#{migration_name}-transaction-duration.json" } |   let(:log_file) { "#{directory_path}/#{migration_version}_#{migration_name}-transaction-duration.json" } | ||||||
|  | @ -78,17 +79,17 @@ RSpec.describe Gitlab::Database::Migrations::Observers::TransactionDuration do | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def run_real_transactions |   def run_real_transactions | ||||||
|     ActiveRecord::Base.transaction do |     ApplicationRecord.transaction do | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def run_sub_transactions |   def run_sub_transactions | ||||||
|     ActiveRecord::Base.transaction(requires_new: true) do |     ApplicationRecord.transaction(requires_new: true) do | ||||||
|     end |     end | ||||||
|   end |   end | ||||||
| 
 | 
 | ||||||
|   def run_transaction |   def run_transaction | ||||||
|     ActiveRecord::Base.connection_pool.with_connection do |connection| |     ApplicationRecord.connection_pool.with_connection do |connection| | ||||||
|       Gitlab::Database::SharedModel.using_connection(connection) do |       Gitlab::Database::SharedModel.using_connection(connection) do | ||||||
|         Gitlab::Database::SharedModel.transaction do |         Gitlab::Database::SharedModel.transaction do | ||||||
|           Gitlab::Database::SharedModel.transaction(requires_new: true) do |           Gitlab::Database::SharedModel.transaction(requires_new: true) do | ||||||
|  |  | ||||||
|  | @ -76,7 +76,7 @@ RSpec.describe Gitlab::Database::Migrations::Runner do | ||||||
|       it 'runs the unapplied migrations in version order', :aggregate_failures do |       it 'runs the unapplied migrations in version order', :aggregate_failures do | ||||||
|         up.run |         up.run | ||||||
| 
 | 
 | ||||||
|         expect(migration_runs.map(&:dir)).to eq([:up, :up]) |         expect(migration_runs.map(&:dir)).to match_array([:up, :up]) | ||||||
|         expect(migration_runs.map(&:version_to_migrate)).to eq(pending_migrations.map(&:version)) |         expect(migration_runs.map(&:version_to_migrate)).to eq(pending_migrations.map(&:version)) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  | @ -101,7 +101,7 @@ RSpec.describe Gitlab::Database::Migrations::Runner do | ||||||
|       it 'runs the applied migrations for the current branch in reverse order', :aggregate_failures do |       it 'runs the applied migrations for the current branch in reverse order', :aggregate_failures do | ||||||
|         down.run |         down.run | ||||||
| 
 | 
 | ||||||
|         expect(migration_runs.map(&:dir)).to eq([:down, :down]) |         expect(migration_runs.map(&:dir)).to match_array([:down, :down]) | ||||||
|         expect(migration_runs.map(&:version_to_migrate)).to eq(applied_migrations_this_branch.reverse.map(&:version)) |         expect(migration_runs.map(&:version_to_migrate)).to eq(applied_migrations_this_branch.reverse.map(&:version)) | ||||||
|       end |       end | ||||||
|     end |     end | ||||||
|  |  | ||||||
|  | @ -0,0 +1,13 @@ | ||||||
|  | # frozen_string_literal: true | ||||||
|  | 
 | ||||||
|  | require 'spec_helper' | ||||||
|  | 
 | ||||||
|  | RSpec.describe Gitlab::Saas do | ||||||
|  |   describe '.canary_toggle_com_url' do | ||||||
|  |     subject { described_class.canary_toggle_com_url } | ||||||
|  | 
 | ||||||
|  |     let(:next_url) { 'https://next.gitlab.com' } | ||||||
|  | 
 | ||||||
|  |     it { is_expected.to eq(next_url) } | ||||||
|  |   end | ||||||
|  | end | ||||||
		Loading…
	
		Reference in New Issue