diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-14 21:09:48 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-14 21:09:48 +0300 |
commit | 319ac09313e73485b47b8da7a67fb27e74f05721 (patch) | |
tree | db1ee3f81f91ac98aaa4ab2270dfbe39f996900a | |
parent | fde3e0435c496af7dc37527f465573abd5657f5a (diff) |
Add latest changes from gitlab-org/gitlab@master
82 files changed, 790 insertions, 457 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index 752ba3c65a8..2818b6be176 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -320,7 +320,7 @@ db:migrate-from-v12.10.0: - export TAG_TO_CHECKOUT="v12.10.0-ee" - '[[ -d "ee/" ]] || export PROJECT_TO_CHECKOUT="gitlab-foss"' - '[[ -d "ee/" ]] || export TAG_TO_CHECKOUT="v12.10.0"' - - git fetch https://gitlab.com/gitlab-org/$PROJECT_TO_CHECKOUT.git $TAG_TO_CHECKOUT + - retry 'git fetch https://gitlab.com/gitlab-org/$PROJECT_TO_CHECKOUT.git $TAG_TO_CHECKOUT' - git checkout -f FETCH_HEAD - sed -i -e "s/gem 'grpc', '~> 1.24.0'/gem 'grpc', '~> 1.30.2'/" Gemfile # Update gRPC for Ruby 2.7 - sed -i -e "s/gem 'google-protobuf', '~> 3.8.0'/gem 'google-protobuf', '~> 3.12.0'/" Gemfile diff --git a/.rubocop_manual_todo.yml b/.rubocop_manual_todo.yml index 1bdc83fc902..506c6042d81 100644 --- a/.rubocop_manual_todo.yml +++ b/.rubocop_manual_todo.yml @@ -37,7 +37,6 @@ Graphql/ResolverType: - 'app/graphql/resolvers/base_resolver.rb' - 'app/graphql/resolvers/ci/jobs_resolver.rb' - 'app/graphql/resolvers/ci/pipeline_stages_resolver.rb' - - 'app/graphql/resolvers/merge_requests_resolver.rb' - 'app/graphql/resolvers/users/group_count_resolver.rb' - 'ee/app/graphql/resolvers/vulnerabilities_base_resolver.rb' diff --git a/app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue b/app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue index bf47d7cf7c0..5953a4fbad8 100644 --- a/app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue +++ b/app/assets/javascripts/feature_flags/components/configure_feature_flags_modal.vue @@ -9,10 +9,10 @@ import { GlSprintf, GlLink, GlIcon, + GlAlert, } from '@gitlab/ui'; import { s__, __ } from '~/locale'; import ModalCopyButton from '~/vue_shared/components/modal_copy_button.vue'; -import Callout from '~/vue_shared/components/callout.vue'; export default { components: { @@ -22,10 +22,10 @@ export default { GlModal, ModalCopyButton, GlIcon, - Callout, GlLoadingIcon, GlSprintf, GlLink, + GlAlert, }, directives: { @@ -153,8 +153,7 @@ export default { </template> </gl-sprintf> </p> - - <callout category="warning"> + <gl-alert variant="warning" class="gl-mb-5" :dismissible="false"> <gl-sprintf :message=" s__( @@ -168,7 +167,7 @@ export default { }}</gl-link> </template> </gl-sprintf> - </callout> + </gl-alert> <gl-form-group :label="$options.translations.apiUrlLabelText" label-for="api-url"> <gl-form-input-group id="api-url" :value="unleashApiUrl" readonly type="text" name="api-url"> <template #append> @@ -212,11 +211,9 @@ export default { <gl-icon name="warning" class="gl-mr-2" /> <span>{{ $options.translations.instanceIdRegenerateError }}</span> </div> - <callout - v-if="canUserRotateToken" - category="danger" - :message="$options.translations.instanceIdRegenerateText" - /> + <gl-alert v-if="canUserRotateToken" variant="danger" class="gl-mb-5" :dismissible="false"> + {{ $options.translations.instanceIdRegenerateText }} + </gl-alert> <p v-if="canUserRotateToken" data-testid="prevent-accident-text"> <gl-sprintf :message=" diff --git a/app/assets/javascripts/feature_flags/components/strategy.vue b/app/assets/javascripts/feature_flags/components/strategy.vue index 9c41dde62e4..ce03248381c 100644 --- a/app/assets/javascripts/feature_flags/components/strategy.vue +++ b/app/assets/javascripts/feature_flags/components/strategy.vue @@ -183,11 +183,11 @@ export default { <span v-if="appliesToAllEnvironments" class="text-secondary gl-mt-3 mt-md-0 ml-md-3"> {{ $options.i18n.allEnvironments }} </span> - <div v-else class="gl-display-flex gl-align-items-center"> + <div v-else class="gl-display-flex gl-align-items-center gl-flex-wrap"> <gl-token v-for="environment in filteredEnvironments" :key="environment.id" - class="gl-mt-3 gl-mr-3 mt-md-0 mr-md-0 ml-md-2 rounded-pill" + class="gl-mt-3 gl-mr-3 gl-mb-3 mt-md-0 mr-md-0 ml-md-2 rounded-pill" @close="removeScope(environment)" > {{ environment.environmentScope }} diff --git a/app/assets/javascripts/issuable_show/components/issuable_body.vue b/app/assets/javascripts/issuable_show/components/issuable_body.vue index e6a05c1ab8b..c084f328f42 100644 --- a/app/assets/javascripts/issuable_show/components/issuable_body.vue +++ b/app/assets/javascripts/issuable_show/components/issuable_body.vue @@ -36,10 +36,18 @@ export default { type: Boolean, required: true, }, + enableAutosave: { + type: Boolean, + required: true, + }, editFormVisible: { type: Boolean, required: true, }, + showFieldTitle: { + type: Boolean, + required: true, + }, descriptionPreviewPath: { type: String, required: true, @@ -57,6 +65,14 @@ export default { return this.issuable.updatedBy; }, }, + methods: { + handleKeydownTitle(e, issuableMeta) { + this.$emit('keydown-title', e, issuableMeta); + }, + handleKeydownDescription(e, issuableMeta) { + this.$emit('keydown-description', e, issuableMeta); + }, + }, }; </script> @@ -67,8 +83,12 @@ export default { v-if="editFormVisible" :issuable="issuable" :enable-autocomplete="enableAutocomplete" + :enable-autosave="enableAutosave" + :show-field-title="showFieldTitle" :description-preview-path="descriptionPreviewPath" :description-help-path="descriptionHelpPath" + @keydown-title="handleKeydownTitle" + @keydown-description="handleKeydownDescription" > <template #edit-form-actions="issuableMeta"> <slot name="edit-form-actions" v-bind="issuableMeta"></slot> diff --git a/app/assets/javascripts/issuable_show/components/issuable_edit_form.vue b/app/assets/javascripts/issuable_show/components/issuable_edit_form.vue index 7b9a83a740f..93e4db8b99c 100644 --- a/app/assets/javascripts/issuable_show/components/issuable_edit_form.vue +++ b/app/assets/javascripts/issuable_show/components/issuable_edit_form.vue @@ -23,6 +23,14 @@ export default { type: Boolean, required: true, }, + enableAutosave: { + type: Boolean, + required: true, + }, + showFieldTitle: { + type: Boolean, + required: true, + }, descriptionPreviewPath: { type: String, required: true, @@ -33,19 +41,27 @@ export default { }, }, data() { - const { title, description } = this.issuable; - return { - title, - description, + title: '', + description: '', }; }, + watch: { + issuable: { + handler(value) { + this.title = value?.title || ''; + this.description = value?.description || ''; + }, + deep: true, + immediate: true, + }, + }, created() { eventHub.$on('update.issuable', this.resetAutosave); eventHub.$on('close.form', this.resetAutosave); }, mounted() { - this.initAutosave(); + if (this.enableAutosave) this.initAutosave(); }, beforeDestroy() { eventHub.$off('update.issuable', this.resetAutosave); @@ -73,6 +89,12 @@ export default { this.autosaveTitle.reset(); this.autosaveDescription.reset(); }, + handleKeydown(e, inputType) { + this.$emit(`keydown-${inputType}`, e, { + issuableTitle: this.title, + issuableDescription: this.description, + }); + }, }, }; </script> @@ -82,9 +104,9 @@ export default { <gl-form-group data-testid="title" :label="__('Title')" - :label-sr-only="true" + :label-sr-only="!showFieldTitle" label-for="issuable-title" - class="col-12" + class="col-12 gl-px-0" > <gl-form-input id="issuable-title" @@ -94,14 +116,16 @@ export default { :aria-label="__('Title')" :autofocus="true" class="qa-title-input" + @keydown="handleKeydown($event, 'title')" /> </gl-form-group> <gl-form-group data-testid="description" :label="__('Description')" - :label-sr-only="true" + :label-sr-only="!showFieldTitle" label-for="issuable-description" - class="col-12 common-note-form" + label-class="gl-pb-0!" + class="col-12 gl-px-0 common-note-form" > <markdown-field :markdown-preview-path="descriptionPreviewPath" @@ -120,11 +144,12 @@ export default { class="note-textarea js-gfm-input js-autosize markdown-area qa-description-textarea" dir="auto" + @keydown="handleKeydown($event, 'description')" ></textarea> </template> </markdown-field> </gl-form-group> - <div data-testid="actions" class="col-12 gl-mt-3 gl-mb-3 clearfix"> + <div data-testid="actions" class="col-12 gl-mt-3 gl-mb-3 gl-px-0 clearfix"> <slot name="edit-form-actions" :issuable-title="title" diff --git a/app/assets/javascripts/issuable_show/components/issuable_show_root.vue b/app/assets/javascripts/issuable_show/components/issuable_show_root.vue index b41f5e270a8..2443338e8c4 100644 --- a/app/assets/javascripts/issuable_show/components/issuable_show_root.vue +++ b/app/assets/javascripts/issuable_show/components/issuable_show_root.vue @@ -35,11 +35,21 @@ export default { required: false, default: false, }, + enableAutosave: { + type: Boolean, + required: false, + default: true, + }, editFormVisible: { type: Boolean, required: false, default: false, }, + showFieldTitle: { + type: Boolean, + required: false, + default: false, + }, descriptionPreviewPath: { type: String, required: false, @@ -51,6 +61,14 @@ export default { default: '', }, }, + methods: { + handleKeydownTitle(e, issuableMeta) { + this.$emit('keydown-title', e, issuableMeta); + }, + handleKeydownDescription(e, issuableMeta) { + this.$emit('keydown-description', e, issuableMeta); + }, + }, }; </script> @@ -77,10 +95,14 @@ export default { :status-icon="statusIcon" :enable-edit="enableEdit" :enable-autocomplete="enableAutocomplete" + :enable-autosave="enableAutosave" :edit-form-visible="editFormVisible" + :show-field-title="showFieldTitle" :description-preview-path="descriptionPreviewPath" :description-help-path="descriptionHelpPath" @edit-issuable="$emit('edit-issuable', $event)" + @keydown-title="handleKeydownTitle" + @keydown-description="handleKeydownDescription" > <template #status-badge> <slot name="status-badge"></slot> diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index c6adf2f231f..30093224631 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -1,12 +1,11 @@ <script> import { throttle, isEmpty } from 'lodash'; import { mapGetters, mapState, mapActions } from 'vuex'; -import { GlLoadingIcon, GlIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui'; +import { GlLoadingIcon, GlIcon, GlSafeHtmlDirective as SafeHtml, GlAlert } from '@gitlab/ui'; import { GlBreakpointInstance as bp } from '@gitlab/ui/dist/utils'; import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; import { polyfillSticky } from '~/lib/utils/sticky'; import CiHeader from '~/vue_shared/components/header_ci_component.vue'; -import Callout from '~/vue_shared/components/callout.vue'; import EmptyState from './empty_state.vue'; import EnvironmentsBlock from './environments_block.vue'; import ErasedBlock from './erased_block.vue'; @@ -22,7 +21,6 @@ export default { name: 'JobPageApp', components: { CiHeader, - Callout, EmptyState, EnvironmentsBlock, ErasedBlock, @@ -34,6 +32,7 @@ export default { Sidebar, GlLoadingIcon, SharedRunner: () => import('ee_component/jobs/components/shared_runner_limit_block.vue'), + GlAlert, }, directives: { SafeHtml, @@ -223,10 +222,14 @@ export default { @clickedSidebarButton="toggleSidebar" /> </div> - - <callout v-if="shouldRenderHeaderCallout"> + <gl-alert + v-if="shouldRenderHeaderCallout" + variant="danger" + class="gl-mt-3" + :dismissible="false" + > <div v-safe-html="job.callout_message"></div> - </callout> + </gl-alert> </header> <!-- EO Header Section --> diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue index 2f050302db5..67b2ed3b596 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue @@ -69,9 +69,6 @@ export default { }, }, methods: { - handleError(errorType) { - this.$emit('error', errorType); - }, setJob(jobName) { this.hoveredJobName = jobName; }, @@ -97,7 +94,7 @@ export default { :linked-pipelines="upstreamPipelines" :column-title="__('Upstream')" :type="$options.pipelineTypeConstants.UPSTREAM" - @error="handleError" + @error="emit('error', errorType)" /> </template> <template #main> @@ -109,6 +106,7 @@ export default { :action="stage.status.action" :job-hovered="hoveredJobName" :pipeline-expanded="pipelineExpanded" + @refreshPipelineGraph="$emit('refreshPipelineGraph')" /> </template> <template #downstream> @@ -119,7 +117,7 @@ export default { :type="$options.pipelineTypeConstants.DOWNSTREAM" @downstreamHovered="setJob" @pipelineExpandToggle="togglePipelineExpanded" - @error="handleError" + @error="emit('error', errorType)" /> </template> </linked-graph-wrapper> diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue index cb2f4d0d623..d98e3aad054 100644 --- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue +++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue @@ -4,7 +4,7 @@ import { __ } from '~/locale'; import { DEFAULT, LOAD_FAILURE } from '../../constants'; import getPipelineDetails from '../../graphql/queries/get_pipeline_details.query.graphql'; import PipelineGraph from './graph_component.vue'; -import { unwrapPipelineData } from './utils'; +import { unwrapPipelineData, toggleQueryPollingByVisibility } from './utils'; export default { name: 'PipelineGraphWrapper', @@ -35,6 +35,7 @@ export default { apollo: { pipeline: { query: getPipelineDetails, + pollInterval: 10000, variables() { return { projectPath: this.pipelineProjectPath, @@ -64,11 +65,24 @@ export default { }; } }, + showLoadingIcon() { + /* + Shows the icon only when the graph is empty, not when it is is + being refetched, for instance, on action completion + */ + return this.$apollo.queries.pipeline.loading && !this.pipeline; + }, + }, + mounted() { + toggleQueryPollingByVisibility(this.$apollo.queries.pipeline); }, methods: { hideAlert() { this.showAlert = false; }, + refreshPipelineGraph() { + this.$apollo.queries.pipeline.refetch(); + }, reportFailure(type) { this.showAlert = true; this.failureType = type; @@ -81,7 +95,12 @@ export default { <gl-alert v-if="showAlert" :variant="alert.variant" @dismiss="hideAlert"> {{ alert.text }} </gl-alert> - <gl-loading-icon v-if="$apollo.queries.pipeline.loading" class="gl-mx-auto gl-my-4" size="lg" /> - <pipeline-graph v-if="pipeline" :pipeline="pipeline" @error="reportFailure" /> + <gl-loading-icon v-if="showLoadingIcon" class="gl-mx-auto gl-my-4" size="lg" /> + <pipeline-graph + v-if="pipeline" + :pipeline="pipeline" + @error="reportFailure" + @refreshPipelineGraph="refreshPipelineGraph" + /> </div> </template> diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue index 58757a88102..7d333087874 100644 --- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue +++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue @@ -3,7 +3,7 @@ import getPipelineDetails from '../../graphql/queries/get_pipeline_details.query import LinkedPipeline from './linked_pipeline.vue'; import { LOAD_FAILURE } from '../../constants'; import { UPSTREAM } from './constants'; -import { unwrapPipelineData } from './utils'; +import { unwrapPipelineData, toggleQueryPollingByVisibility } from './utils'; export default { components: { @@ -67,6 +67,7 @@ export default { this.$apollo.addSmartQuery('currentPipeline', { query: getPipelineDetails, + pollInterval: 10000, variables() { return { projectPath, @@ -83,6 +84,8 @@ export default { this.$emit('error', LOAD_FAILURE); }, }); + + toggleQueryPollingByVisibility(this.$apollo.queries.currentPipeline); }, isExpanded(id) { return Boolean(this.currentPipeline?.id && id === this.currentPipeline.id); diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue index 830ad219574..b9bddc94ce4 100644 --- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue +++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue @@ -79,6 +79,7 @@ export default { :tooltip-text="action.title" :link="action.path" class="js-stage-action stage-action rounded" + @pipelineActionRequestComplete="$emit('refreshPipelineGraph')" /> </div> </template> @@ -96,6 +97,7 @@ export default { :job-hovered="jobHovered" :pipeline-expanded="pipelineExpanded" css-class-job-name="gl-build-content" + @pipelineActionRequestComplete="$emit('refreshPipelineGraph')" /> <job-group-dropdown v-else :group="group" /> </div> diff --git a/app/assets/javascripts/pipelines/components/graph/utils.js b/app/assets/javascripts/pipelines/components/graph/utils.js index 7bf44b160ef..32588feb426 100644 --- a/app/assets/javascripts/pipelines/components/graph/utils.js +++ b/app/assets/javascripts/pipelines/components/graph/utils.js @@ -1,3 +1,4 @@ +import Visibility from 'visibilityjs'; import { getIdFromGraphQLId } from '~/graphql_shared/utils'; import { unwrapStagesWithNeeds } from '../unwrapping_utils'; @@ -40,4 +41,17 @@ const unwrapPipelineData = (mainPipelineProjectPath, data) => { }; }; -export { unwrapPipelineData }; +const toggleQueryPollingByVisibility = (queryRef, interval = 10000) => { + const stopStartQuery = query => { + if (!Visibility.hidden()) { + query.startPolling(interval); + } else { + query.stopPolling(); + } + }; + + stopStartQuery(queryRef); + Visibility.change(stopStartQuery.bind(null, queryRef)); +}; + +export { unwrapPipelineData, toggleQueryPollingByVisibility }; diff --git a/app/assets/javascripts/pipelines/pipeline_details_graph.js b/app/assets/javascripts/pipelines/pipeline_details_graph.js index b6ebd271802..1b296c305cb 100644 --- a/app/assets/javascripts/pipelines/pipeline_details_graph.js +++ b/app/assets/javascripts/pipelines/pipeline_details_graph.js @@ -7,7 +7,12 @@ import { GRAPHQL } from './components/graph/constants'; Vue.use(VueApollo); const apolloProvider = new VueApollo({ - defaultClient: createDefaultClient(), + defaultClient: createDefaultClient( + {}, + { + batchMax: 2, + }, + ), }); const createPipelinesDetailApp = (selector, pipelineProjectPath, pipelineIid) => { diff --git a/app/assets/javascripts/vue_shared/components/callout.vue b/app/assets/javascripts/vue_shared/components/callout.vue deleted file mode 100644 index 56bafebf4ce..00000000000 --- a/app/assets/javascripts/vue_shared/components/callout.vue +++ /dev/null @@ -1,24 +0,0 @@ -<script> -const calloutVariants = ['danger', 'success', 'info', 'warning']; - -export default { - props: { - category: { - type: String, - required: false, - default: calloutVariants[0], - validator: value => calloutVariants.includes(value), - }, - message: { - type: String, - required: false, - default: '', - }, - }, -}; -</script> -<template> - <div :class="`bs-callout bs-callout-${category}`" role="alert" aria-live="assertive"> - {{ message }} <slot></slot> - </div> -</template> diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb index 1ddc9d567e0..ab1cf63c885 100644 --- a/app/controllers/projects/cycle_analytics_controller.rb +++ b/app/controllers/projects/cycle_analytics_controller.rb @@ -17,8 +17,6 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController def show @cycle_analytics = ::CycleAnalytics::ProjectLevel.new(@project, options: options(cycle_analytics_project_params)) - @cycle_analytics_no_data = @cycle_analytics.no_stats? - respond_to do |format| format.html do Gitlab::UsageDataCounters::CycleAnalyticsCounter.count(:views) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 61ec72a0633..ee25a2006ad 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -43,7 +43,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo push_frontend_feature_flag(:core_security_mr_widget_downloads, @project, default_enabled: true) push_frontend_feature_flag(:remove_resolve_note, @project, default_enabled: true) push_frontend_feature_flag(:test_failure_history, @project) - push_frontend_feature_flag(:diffs_gradual_load, @project) + push_frontend_feature_flag(:diffs_gradual_load, @project, default_enabled: true) record_experiment_user(:invite_members_version_a) record_experiment_user(:invite_members_version_b) diff --git a/app/graphql/resolvers/merge_requests_resolver.rb b/app/graphql/resolvers/merge_requests_resolver.rb index 4629fed2a4e..98c95565778 100644 --- a/app/graphql/resolvers/merge_requests_resolver.rb +++ b/app/graphql/resolvers/merge_requests_resolver.rb @@ -4,6 +4,8 @@ module Resolvers class MergeRequestsResolver < BaseResolver include ResolvesMergeRequests + type ::Types::MergeRequestType.connection_type, null: true + alias_method :project, :synchronized_object def self.accept_assignee diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 35730cff849..0c5823894c5 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -242,7 +242,7 @@ module BlobHelper def copy_blob_source_button(blob) return unless blob.rendered_as_text?(ignore_errors: false) - clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}']", class: "btn btn-sm js-copy-blob-source-btn", title: _("Copy file contents")) + clipboard_button(target: ".blob-content[data-blob-id='#{blob.id}'] > pre", class: "btn btn-sm js-copy-blob-source-btn", title: _("Copy file contents")) end def open_raw_blob_button(blob) diff --git a/app/models/concerns/can_move_repository_storage.rb b/app/models/concerns/can_move_repository_storage.rb new file mode 100644 index 00000000000..52c3a4106e3 --- /dev/null +++ b/app/models/concerns/can_move_repository_storage.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +module CanMoveRepositoryStorage + extend ActiveSupport::Concern + + RepositoryReadOnlyError = Class.new(StandardError) + + # Tries to set repository as read_only, checking for existing Git transfers in + # progress beforehand. Setting a repository read-only will fail if it is + # already in that state. + # + # @return nil. Failures will raise an exception + def set_repository_read_only!(skip_git_transfer_check: false) + with_lock do + raise RepositoryReadOnlyError, _('Git transfer in progress') if + !skip_git_transfer_check && git_transfer_in_progress? + + raise RepositoryReadOnlyError, _('Repository already read-only') if + self.class.where(id: id).pick(:repository_read_only) + + raise ActiveRecord::RecordNotSaved, _('Database update failed') unless + update_column(:repository_read_only, true) + + nil + end + end + + # Set repository as writable again. Unlike setting it read-only, this will + # succeed if the repository is already writable. + def set_repository_writable! + with_lock do + raise ActiveRecord::RecordNotSaved, _('Database update failed') unless + update_column(:repository_read_only, false) + + nil + end + end + + def git_transfer_in_progress? + reference_counter(type: repository.repo_type).value > 0 + end + + def reference_counter(type:) + Gitlab::ReferenceCounter.new(type.identifier_for_container(self)) + end +end diff --git a/app/models/cycle_analytics/level_base.rb b/app/models/cycle_analytics/level_base.rb index bc7aa57a0a1..63c55e2ca64 100644 --- a/app/models/cycle_analytics/level_base.rb +++ b/app/models/cycle_analytics/level_base.rb @@ -59,10 +59,6 @@ module CycleAnalytics end end - def no_stats? - stats.all? { |hash| hash[:value].nil? } - end - def [](stage_name) if Feature.enabled?(:new_project_level_vsa_backend, resource_parent) StageAdapter.new(build_stage(stage_name), options) diff --git a/app/models/project.rb b/app/models/project.rb index b4b23831610..80f4137db14 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -19,6 +19,7 @@ class Project < ApplicationRecord include Presentable include HasRepository include HasWiki + include CanMoveRepositoryStorage include Routable include GroupDescendant include Gitlab::SQL::Pattern @@ -2112,39 +2113,6 @@ class Project < ApplicationRecord (auto_devops || build_auto_devops)&.predefined_variables end - RepositoryReadOnlyError = Class.new(StandardError) - - # Tries to set repository as read_only, checking for existing Git transfers in - # progress beforehand. Setting a repository read-only will fail if it is - # already in that state. - # - # @return nil. Failures will raise an exception - def set_repository_read_only!(skip_git_transfer_check: false) - with_lock do - raise RepositoryReadOnlyError, _('Git transfer in progress') if - !skip_git_transfer_check && git_transfer_in_progress? - - raise RepositoryReadOnlyError, _('Repository already read-only') if - self.class.where(id: id).pick(:repository_read_only) - - raise ActiveRecord::RecordNotSaved, _('Database update failed') unless - update_column(:repository_read_only, true) - - nil - end - end - - # Set repository as writable again. Unlike setting it read-only, this will - # succeed if the repository is already writable. - def set_repository_writable! - with_lock do - raise ActiveRecord::RecordNotSaved, _('Database update failed') unless - update_column(:repository_read_only, false) - - nil - end - end - def pushes_since_gc Gitlab::Redis::SharedState.with { |redis| redis.get(pushes_since_gc_redis_shared_state_key).to_i } end @@ -2294,6 +2262,7 @@ class Project < ApplicationRecord end end + override :git_transfer_in_progress? def git_transfer_in_progress? GL_REPOSITORY_TYPES.any? do |type| reference_counter(type: type).value > 0 @@ -2306,10 +2275,6 @@ class Project < ApplicationRecord @storage = nil if storage_version_changed? end - def reference_counter(type: Gitlab::GlRepository::PROJECT) - Gitlab::ReferenceCounter.new(type.identifier_for_container(self)) - end - def badges return project_badges unless group diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 1a985ad1067..1bb96a4e02b 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -15,6 +15,7 @@ class Snippet < ApplicationRecord include FromUnion include IgnorableColumns include HasRepository + include CanMoveRepositoryStorage include AfterCommitQueue extend ::Gitlab::Utils::Override diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml index 5f7ed46297b..87c0933747d 100644 --- a/app/views/projects/_commit_button.html.haml +++ b/app/views/projects/_commit_button.html.haml @@ -1,7 +1,7 @@ .form-actions - = button_tag 'Commit changes', id: 'commit-changes', class: 'btn commit-btn js-commit-button btn-success qa-commit-button' + = button_tag 'Commit changes', id: 'commit-changes', class: 'gl-button btn btn-success js-commit-button qa-commit-button' = link_to 'Cancel', cancel_path, - class: 'btn btn-cancel', data: {confirm: leave_edit_message} + class: 'gl-button btn btn-default btn-cancel', data: {confirm: leave_edit_message} = render 'shared/projects/edit_information' diff --git a/app/views/projects/_customize_workflow.html.haml b/app/views/projects/_customize_workflow.html.haml index a41791f0eca..8e4e5ca93e0 100644 --- a/app/views/projects/_customize_workflow.html.haml +++ b/app/views/projects/_customize_workflow.html.haml @@ -5,4 +5,4 @@ %p Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and pipelines, GitLab can help manage your workflow from idea to production! - if can?(current_user, :admin_project, @project) - = link_to "Get started", edit_project_path(@project), class: "btn btn-success" + = link_to "Get started", edit_project_path(@project), class: "gl-button btn btn-success" diff --git a/app/views/projects/_fork_suggestion.html.haml b/app/views/projects/_fork_suggestion.html.haml index 0b616a0c1ce..9e6ff4a5d7a 100644 --- a/app/views/projects/_fork_suggestion.html.haml +++ b/app/views/projects/_fork_suggestion.html.haml @@ -6,6 +6,6 @@ edit files in this project directly. Please fork this project, make your changes there, and submit a merge request. - = link_to 'Fork', nil, method: :post, class: 'js-fork-suggestion-button btn btn-grouped btn-inverted btn-success' - %button.js-cancel-fork-suggestion-button.btn.btn-grouped{ type: 'button' } + = link_to 'Fork', nil, method: :post, class: 'js-fork-suggestion-button gl-button btn btn-grouped btn-inverted btn-success' + %button.js-cancel-fork-suggestion-button.gl-button.btn.btn-grouped{ type: 'button' } Cancel diff --git a/app/views/projects/_import_project_pane.html.haml b/app/views/projects/_import_project_pane.html.haml index 6cff715f1a0..27d75591d3e 100644 --- a/app/views/projects/_import_project_pane.html.haml +++ b/app/views/projects/_import_project_pane.html.haml @@ -8,67 +8,77 @@ .import-buttons - if gitlab_project_import_enabled? .import_gitlab_project.has-tooltip{ data: { container: 'body' } } - = link_to new_import_gitlab_project_path, class: 'btn btn_import_gitlab_project project-submit', **tracking_attrs(track_label, 'click_button', 'gitlab_export') do - = sprite_icon('tanuki') + = link_to new_import_gitlab_project_path, class: 'gl-button btn-default btn btn_import_gitlab_project project-submit', **tracking_attrs(track_label, 'click_button', 'gitlab_export') do + .gl-button-icon + = sprite_icon('tanuki') = _("GitLab export") - if github_import_enabled? %div - = link_to new_import_github_path, class: 'btn js-import-github', **tracking_attrs(track_label, 'click_button', 'github') do - = sprite_icon('github') + = link_to new_import_github_path, class: 'gl-button btn-default btn js-import-github', **tracking_attrs(track_label, 'click_button', 'github') do + .gl-button-icon + = sprite_icon('github') GitHub - if bitbucket_import_enabled? %div - = link_to status_import_bitbucket_path, class: "btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}", + = link_to status_import_bitbucket_path, class: "gl-button btn-default btn import_bitbucket #{'how_to_import_link' unless bitbucket_import_configured?}", **tracking_attrs(track_label, 'click_button', 'bitbucket_cloud') do - = sprite_icon('bitbucket') + .gl-button-icon + = sprite_icon('bitbucket') Bitbucket Cloud - unless bitbucket_import_configured? = render 'projects/bitbucket_import_modal' - if bitbucket_server_import_enabled? %div - = link_to status_import_bitbucket_server_path, class: "btn import_bitbucket", **tracking_attrs(track_label, 'click_button', 'bitbucket_server') do - = sprite_icon('bitbucket') + = link_to status_import_bitbucket_server_path, class: "gl-button btn-default btn import_bitbucket", **tracking_attrs(track_label, 'click_button', 'bitbucket_server') do + .gl-button-icon + = sprite_icon('bitbucket') Bitbucket Server %div - if gitlab_import_enabled? %div - = link_to status_import_gitlab_path, class: "btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}", + = link_to status_import_gitlab_path, class: "gl-button btn-default btn import_gitlab #{'how_to_import_link' unless gitlab_import_configured?}", **tracking_attrs(track_label, 'click_button', 'gitlab_com') do - = sprite_icon('tanuki') + .gl-button-icon + = sprite_icon('tanuki') = _("GitLab.com") - unless gitlab_import_configured? = render 'projects/gitlab_import_modal' - if fogbugz_import_enabled? %div - = link_to new_import_fogbugz_path, class: 'btn import_fogbugz', **tracking_attrs(track_label, 'click_button', 'fogbugz') do - = sprite_icon('bug') + = link_to new_import_fogbugz_path, class: 'gl-button btn-default btn import_fogbugz', **tracking_attrs(track_label, 'click_button', 'fogbugz') do + .gl-button-icon + = sprite_icon('bug') FogBugz - if gitea_import_enabled? %div - = link_to new_import_gitea_path, class: 'btn import_gitea', **tracking_attrs(track_label, 'click_button', 'gitea') do - = custom_icon('gitea_logo') + = link_to new_import_gitea_path, class: 'gl-button btn-default btn import_gitea', **tracking_attrs(track_label, 'click_button', 'gitea') do + .gl-button-icon + = custom_icon('gitea_logo') Gitea - if git_import_enabled? %div - %button.btn.btn-svg.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' }, **tracking_attrs(track_label, 'click_button', 'repo_url') } - = sprite_icon('link', css_class: 'gl-icon') + %button.gl-button.btn-default.btn.btn-svg.js-toggle-button.js-import-git-toggle-button{ type: "button", data: { toggle_open_class: 'active' }, **tracking_attrs(track_label, 'click_button', 'repo_url') } + .gl-button-icon + = sprite_icon('link', css_class: 'gl-icon') = _('Repo by URL') - if manifest_import_enabled? %div - = link_to new_import_manifest_path, class: 'btn import_manifest', **tracking_attrs(track_label, 'click_button', 'manifest_file') do - = sprite_icon('doc-text') + = link_to new_import_manifest_path, class: 'gl-button btn-default btn import_manifest', **tracking_attrs(track_label, 'click_button', 'manifest_file') do + .gl-button-icon + = sprite_icon('doc-text') Manifest file - if phabricator_import_enabled? %div - = link_to new_import_phabricator_path, class: 'btn import_phabricator', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "phabricator" } do - = custom_icon('issues') + = link_to new_import_phabricator_path, class: 'gl-button btn-default btn import_phabricator', data: { track_label: "#{track_label}", track_event: "click_button", track_property: "phabricator" } do + .gl-button-icon + = custom_icon('issues') = _("Phabricator Tasks") diff --git a/app/views/projects/cycle_analytics/show.html.haml b/app/views/projects/cycle_analytics/show.html.haml index e589ca3be25..fc3710d3609 100644 --- a/app/views/projects/cycle_analytics/show.html.haml +++ b/app/views/projects/cycle_analytics/show.html.haml @@ -2,13 +2,6 @@ - add_page_specific_style 'page_bundles/cycle_analytics' #cycle-analytics{ "v-cloak" => "true", data: { request_path: project_cycle_analytics_path(@project) } } - - if @cycle_analytics_no_data - %banner{ "v-if" => "!isOverviewDialogDismissed", - "documentation-link": help_page_path('user/analytics/value_stream_analytics.md'), - "v-on:dismiss-overview-dialog" => "dismissOverviewDialog()" } - .mb-3 - %h3 - = _("Value Stream Analytics") %gl-loading-icon{ "v-show" => "isLoading", "size" => "lg" } .wrapper{ "v-show" => "!isLoading && !hasError" } .card diff --git a/app/views/projects/graphs/charts.html.haml b/app/views/projects/graphs/charts.html.haml index 24d92e947bc..a92b02701c5 100644 --- a/app/views/projects/graphs/charts.html.haml +++ b/app/views/projects/graphs/charts.html.haml @@ -25,7 +25,7 @@ = (_("Code coverage statistics for master %{start_date} - %{end_date}") % {start_date: start_date, end_date: end_date}) - download_path = capture do #{@daily_coverage_options[:download_path]} - %a.btn.btn-sm{ href: "#{download_path}?#{@daily_coverage_options[:base_params].to_query}" } + %a.btn.gl-button.btn-sm{ href: "#{download_path}?#{@daily_coverage_options[:base_params].to_query}" } %small = _("Download raw data (.csv)") #js-code-coverage-chart{ data: { graph_endpoint: "#{@daily_coverage_options[:graph_api_path]}?#{@daily_coverage_options[:base_params].to_query}" } } diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index a73e367733b..c7508ef4d47 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -3,6 +3,6 @@ .sub-header-block.bg-gray-light.gl-p-5 .tree-ref-holder.inline.vertical-align-middle = render 'shared/ref_switcher', destination: 'graphs' - = link_to s_('Commits|History'), project_commits_path(@project, current_ref), class: 'btn' + = link_to s_('Commits|History'), project_commits_path(@project, current_ref), class: 'btn gl-button' .js-contributors-graph{ class: container_class, 'data-project-graph-path': project_graph_path(@project, current_ref, format: :json),'data-project-branch': current_ref } diff --git a/changelogs/unreleased/-231201-projects-graphs.yml b/changelogs/unreleased/-231201-projects-graphs.yml new file mode 100644 index 00000000000..1944d749a05 --- /dev/null +++ b/changelogs/unreleased/-231201-projects-graphs.yml @@ -0,0 +1,5 @@ +--- +title: Apply GitLab UI button styles to buttons in app/views/projects/graphs directory +merge_request: 44295 +author: Lakshit +type: other diff --git a/changelogs/unreleased/231179-projects-buttons.yml b/changelogs/unreleased/231179-projects-buttons.yml new file mode 100644 index 00000000000..c56384ab2bc --- /dev/null +++ b/changelogs/unreleased/231179-projects-buttons.yml @@ -0,0 +1,5 @@ +--- +title: Adds gitlab ui classes to project dir buttons +merge_request: 49939 +author: +type: other diff --git a/changelogs/unreleased/242029-migrate-to-glalert-for-callout.yml b/changelogs/unreleased/242029-migrate-to-glalert-for-callout.yml new file mode 100644 index 00000000000..e773e2acdd1 --- /dev/null +++ b/changelogs/unreleased/242029-migrate-to-glalert-for-callout.yml @@ -0,0 +1,5 @@ +--- +title: Migrate bs-callout to GlAlert for components using app/assets/javascripts/vue_shared/components/callout.vue +merge_request: 49732 +author: Gary Bell @garybell +type: other diff --git a/changelogs/unreleased/293685-many-environments-strategies-break-the-row.yml b/changelogs/unreleased/293685-many-environments-strategies-break-the-row.yml new file mode 100644 index 00000000000..06bd0453bf7 --- /dev/null +++ b/changelogs/unreleased/293685-many-environments-strategies-break-the-row.yml @@ -0,0 +1,5 @@ +--- +title: Make the strategies env wrap +merge_request: 49951 +author: +type: fixed diff --git a/changelogs/unreleased/fix-no-stats-in-vsa.yml b/changelogs/unreleased/fix-no-stats-in-vsa.yml new file mode 100644 index 00000000000..d735cb3a0d7 --- /dev/null +++ b/changelogs/unreleased/fix-no-stats-in-vsa.yml @@ -0,0 +1,5 @@ +--- +title: Remove initial data check on project level value stream page +merge_request: 49936 +author: +type: performance diff --git a/changelogs/unreleased/jdb-fix-copy-to-clipboard-ff.yml b/changelogs/unreleased/jdb-fix-copy-to-clipboard-ff.yml new file mode 100644 index 00000000000..07d046fb729 --- /dev/null +++ b/changelogs/unreleased/jdb-fix-copy-to-clipboard-ff.yml @@ -0,0 +1,5 @@ +--- +title: Fix copy to clipboard on Firefox +merge_request: 49648 +author: +type: fixed diff --git a/changelogs/unreleased/ph-diffsGradualLoadDefaultEnabled.yml b/changelogs/unreleased/ph-diffsGradualLoadDefaultEnabled.yml new file mode 100644 index 00000000000..cf2bf33a9e7 --- /dev/null +++ b/changelogs/unreleased/ph-diffsGradualLoadDefaultEnabled.yml @@ -0,0 +1,5 @@ +--- +title: Gradually load more diffs async +merge_request: 49476 +author: +type: changed diff --git a/config/feature_flags/development/diffs_gradual_load.yml b/config/feature_flags/development/diffs_gradual_load.yml index 2f615d7d5ff..05246639069 100644 --- a/config/feature_flags/development/diffs_gradual_load.yml +++ b/config/feature_flags/development/diffs_gradual_load.yml @@ -5,4 +5,4 @@ rollout_issue_url: milestone: '13.7' type: development group: group::code review -default_enabled: false +default_enabled: true diff --git a/doc/development/README.md b/doc/development/README.md index eead47f4a45..7adcfe091c6 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -11,20 +11,24 @@ description: "Development Guidelines: learn how to contribute to GitLab." Learn the processes and technical information needed for contributing to GitLab. -This content is intended for members of the GitLab Team as well as community contributors. -Content specific to the GitLab Team should instead be included in the [Handbook](https://about.gitlab.com/handbook/). +This content is intended for members of the GitLab Team as well as community +contributors. Content specific to the GitLab Team should instead be included in +the [Handbook](https://about.gitlab.com/handbook/). -For information on using GitLab to work on your own software projects, see the [GitLab user documentation](../user/index.md). +For information on using GitLab to work on your own software projects, see the +[GitLab user documentation](../user/index.md). For information on working with the GitLab APIs, see the [API documentation](../api/README.md). -For information on how to install, configure, update, and upgrade your own GitLab instance, see the [administration documentation](../administration/index.md). +For information about how to install, configure, update, and upgrade your own +GitLab instance, see the [administration documentation](../administration/index.md). ## Get started -- Set up the GitLab development environment with [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/master/README.md) +- Set up the GitLab development environment with the + [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit/-/blob/master/README.md) - [GitLab contributing guide](contributing/index.md) - - [Issues workflow](contributing/issue_workflow.md) for more information on: + - [Issues workflow](contributing/issue_workflow.md) for more information about: - Issue tracker guidelines. - Triaging. - Labels. @@ -33,7 +37,7 @@ For information on how to install, configure, update, and upgrade your own GitLa - Regression issues. - Technical or UX debt. - [Merge requests workflow](contributing/merge_request_workflow.md) for more - information on: + information about: - Merge request guidelines. - Contribution acceptance criteria. - Definition of done. @@ -48,8 +52,10 @@ For information on how to install, configure, update, and upgrade your own GitLa **Must-reads:** - [Guide on adapting existing and introducing new components](architecture.md#adapting-existing-and-introducing-new-components) -- [Code review guidelines](code_review.md) for reviewing code and having code reviewed -- [Database review guidelines](database_review.md) for reviewing database-related changes and complex SQL queries, and having them reviewed +- [Code review guidelines](code_review.md) for reviewing code and having code + reviewed +- [Database review guidelines](database_review.md) for reviewing + database-related changes and complex SQL queries, and having them reviewed - [Secure coding guidelines](secure_coding_guidelines.md) - [Pipelines for the GitLab project](pipelines.md) @@ -66,20 +72,80 @@ Complementary reads: ### Development guidelines review -When you submit a change to the GitLab development guidelines, request a review -from: +When you submit a change to GitLab's development guidelines, the people +you ask for reviews from depend on the level of change, as described below. -- A member of your team or group, to check for technical accuracy. -- For **significant** changes or proposals, request review from: - - Engineering managers (FE, BE, DB, Security, UX, and others), according to the subject or process you're proposing. - - The VP of Development (DRI) ([@clefelhocz1](https://gitlab.com/clefelhocz1)), for - final approval of the new or changed guidelines. -- The [Technical Writer assigned to dev guidelines](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments-to-development-guidelines), - to review the content for consistency and adherence to documentation guidelines. +#### Wording, style, or link changes + +Not all changes require extensive review. For example, MRs that don't change the +content's meaning or function can be reviewed, approved, and merged by any +maintainer or Technical Writer. These can include: + +- Typo fixes. +- Clarifying links, such as to external programming language documentation. +- Changes to comply with the [Documentation Style Guide](documentation/index.md) + that don't change the intent of the documentation page. + +#### Specific changes + +If the MR proposes changes limited to a particular stage, group, or team, +request a review and approval from an experienced GitLab Team Member in that +group. For example, if you're documenting a new internal API used exclusively by +a given group, request an engineering review from one of the group's members. + +After the engineering review is complete, assign the MR to the +[Technical Writer associated with the stage and group](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments) +in the modified documentation page's metadata. + +If you have questions or need further input, request a review from the +Technical Writer assigned to the [Development Guidelines](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments-to-development-guidelines). + +#### Broader changes + +Some changes affect more than one group. For example: + +- Changes to [code review guidelines](code_review.md). +- Changes to [commit message guidelines](contributing/merge_request_workflow.md#commit-messages-guidelines). +- Changes to guidelines in [feature flags in development of GitLab](feature_flags/). +- Changes to [feature flags documentation guidelines](documentation/feature_flags.md). + +In these cases, use the following workflow: + +1. Request a peer review from a member of your team. +1. Request a review and approval of an Engineering Manager (EM) + or Staff Engineer who's responsible for the area in question: + + - [Frontend](https://about.gitlab.com/handbook/engineering/frontend/) + - [Backend](https://about.gitlab.com/handbook/engineering/) + - [Database](https://about.gitlab.com/handbook/engineering/development/database/) + - [User Experience (UX)](https://about.gitlab.com/handbook/engineering/ux/) + - [Security](https://about.gitlab.com/handbook/engineering/security/) + - [Quality](https://about.gitlab.com/handbook/engineering/quality/) + - [Infrastructure](https://about.gitlab.com/handbook/engineering/infrastructure/) + - [Technical Writing](https://about.gitlab.com/handbook/engineering/ux/technical-writing/) + + You can skip this step for MRs authored by EMs or Staff Engineers responsible + for their area. + + If there are several affected groups, you may need approvals at the + EM/Staff Engineer level from each affected area. + +1. After completing the reviews, consult with the EM/Staff Engineer + author / approver of the MR. + + If this is a significant change across multiple areas, request final review + and approval from the VP of Development, the DRI for Development Guidelines, + @clefelhocz1. + +1. After all approvals are complete, assign the merge request to the + Technical Writer for [Development Guidelines](https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments-to-development-guidelines) + for final content review and merge. The Technical Writer may ask for + additional approvals as previously suggested before merging the MR. ## UX and Frontend guides -- [GitLab Design System](https://design.gitlab.com/) for building GitLab with existing CSS styles and elements +- [GitLab Design System](https://design.gitlab.com/), for building GitLab with + existing CSS styles and elements - [Frontend guidelines](fe_guide/index.md) - [Emoji guide](fe_guide/emojis.md) @@ -89,7 +155,8 @@ from: - [Issuable-like Rails models](issuable-like-models.md) - [Logging](logging.md) - [API style guide](api_styleguide.md) for contributing to the API -- [GraphQL API style guide](api_graphql_styleguide.md) for contributing to the [GraphQL API](../api/graphql/index.md) +- [GraphQL API style guide](api_graphql_styleguide.md) for contributing to the + [GraphQL API](../api/graphql/index.md) - [Sidekiq guidelines](sidekiq_style_guide.md) for working with Sidekiq workers - [Working with Gitaly](gitaly.md) - [Manage feature flags](feature_flags/index.md) @@ -101,7 +168,8 @@ from: - [Sidekiq debugging](../administration/troubleshooting/sidekiq.md) - [Accessing session data](session.md) - [Gotchas](gotchas.md) to avoid -- [Avoid modules with instance variables](module_with_instance_variables.md) if possible +- [Avoid modules with instance variables](module_with_instance_variables.md), if + possible - [How to dump production data to staging](db_dump.md) - [Working with the GitHub importer](github_importer.md) - [Import/Export development documentation](import_export.md) @@ -147,8 +215,9 @@ from: for ensuring merge requests do not negatively impact GitLab performance - [Profiling](profiling.md) a URL, measuring performance using Sherlock, or tracking down N+1 queries using Bullet. -- [Cached queries guidelines](cached_queries.md), for tracking down N+1 queries masked by query caching, memory profiling and why should - we avoid cached queries. +- [Cached queries guidelines](cached_queries.md), for tracking down N+1 queries + masked by query caching, memory profiling and why should we avoid cached + queries. ## Database guides diff --git a/doc/development/code_review.md b/doc/development/code_review.md index 56cd5a9d3cb..00f4cf90481 100644 --- a/doc/development/code_review.md +++ b/doc/development/code_review.md @@ -468,7 +468,7 @@ Enterprise Edition instance. This has some implications: 1. Similarly, if you need to remove a worker, stop it from being scheduled in one release, then remove it in the next. This allows existing jobs to execute. - 1. Don't forget, not every instance will upgrade to every intermediate version + 1. Don't forget, not every instance is upgraded to every intermediate version (some people may go from X.1.0 to X.10.0, or even try bigger upgrades!), so try to be liberal in accepting the old format if it is cheap to do so. 1. **Cached values** may persist across releases. If you are changing the type a @@ -480,12 +480,12 @@ Enterprise Edition instance. This has some implications: 1. Try to avoid that, and add to `ApplicationSetting` instead. 1. Ensure that it is also [added to Omnibus](https://docs.gitlab.com/omnibus/settings/gitlab.yml.html#adding-a-new-setting-to-gitlab-yml). -1. **Filesystem access** can be slow, so try to avoid +1. **File system access** can be slow, so try to avoid [shared files](shared_files.md) when an alternative solution is available. ### Review turnaround time -Since [unblocking others is always a top priority](https://about.gitlab.com/handbook/values/#global-optimization), +Because [unblocking others is always a top priority](https://about.gitlab.com/handbook/values/#global-optimization), reviewers are expected to review assigned merge requests in a timely manner, even when this may negatively impact their other tasks and priorities. diff --git a/doc/development/diffs.md b/doc/development/diffs.md index f1f165f48c4..2f5f3fa21b6 100644 --- a/doc/development/diffs.md +++ b/doc/development/diffs.md @@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Working with diffs -Currently we rely on different sources to present diffs, these include: +We rely on different sources to present diffs. These include: - Gitaly service - Database (through `merge_request_diff_files`) @@ -63,8 +63,7 @@ of hitting the repository every time we need the diff of the file, we: ## Diff limits As explained above, we limit single diff files and the size of the whole diff. There are scenarios where we collapse the diff file, -and cases where the diff file is not presented at all, and the user is guided to the Blob view. Here we'll go into details about -these limits. +and cases where the diff file is not presented at all, and the user is guided to the Blob view. ### Diff collection limits @@ -74,7 +73,7 @@ Limits that act onto all diff files collection. Files number, lines number and f Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_files] = 100 ``` -File diffs will be collapsed (but be expandable) if 100 files have already been rendered. +File diffs are collapsed (but are expandable) if 100 files have already been rendered. ```ruby Gitlab::Git::DiffCollection.collection_limits[:safe_max_lines] = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000 @@ -86,28 +85,28 @@ File diffs will be collapsed (but be expandable) if 5000 lines have already been Gitlab::Git::DiffCollection.collection_limits[:safe_max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:safe_max_files] * 5.kilobytes = 500.kilobytes ``` -File diffs will be collapsed (but be expandable) if 500 kilobytes have already been rendered. +File diffs are collapsed (but be expandable) if 500 kilobytes have already been rendered. ```ruby Gitlab::Git::DiffCollection.collection_limits[:max_files] = Commit::DIFF_HARD_LIMIT_FILES = 1000 ``` -No more files will be rendered at all if 1000 files have already been rendered. +No more files are rendered at all if 1000 files have already been rendered. ```ruby Gitlab::Git::DiffCollection.collection_limits[:max_lines] = Commit::DIFF_HARD_LIMIT_LINES = 50000 ``` -No more files will be rendered at all if 50,000 lines have already been rendered. +No more files are rendered at all if 50,000 lines have already been rendered. ```ruby Gitlab::Git::DiffCollection.collection_limits[:max_bytes] = Gitlab::Git::DiffCollection.collection_limits[:max_files] * 5.kilobytes = 5000.kilobytes ``` -No more files will be rendered at all if 5 megabytes have already been rendered. +No more files are rendered at all if 5 megabytes have already been rendered. -All collection limit parameters are currently sent and applied on Gitaly. That is, once the limit is surpassed, -Gitaly will only return the safe amount of data to be persisted on `merge_request_diff_files`. +All collection limit parameters are sent and applied on Gitaly. That is, after the limit is surpassed, +Gitaly only returns the safe amount of data to be persisted on `merge_request_diff_files`. ### Individual diff file limits @@ -117,24 +116,24 @@ Limits that act onto each diff file of a collection. Files number, lines number Diff patches are collapsed when surpassing 10% of the value set in `ApplicationSettings#diff_max_patch_bytes`. That is, it's equivalent to 10kb if the maximum allowed value is 100kb. -The diff will still be persisted and expandable if the patch size doesn't +The diff is persisted and expandable if the patch size doesn't surpass `ApplicationSettings#diff_max_patch_bytes`. Although this nomenclature (Collapsing) is also used on Gitaly, this limit is only used on GitLab (hardcoded - not sent to Gitaly). -Gitaly will only return `Diff.Collapsed` (RPC) when surpassing collection limits. +Gitaly only returns `Diff.Collapsed` (RPC) when surpassing collection limits. #### Not expandable patches (too large) The patch not be rendered if it's larger than `ApplicationSettings#diff_max_patch_bytes`. -Users will see a `This source diff could not be displayed because it is too large` message. +Users see a `This source diff could not be displayed because it is too large` message. ```ruby Commit::DIFF_SAFE_LINES = Gitlab::Git::DiffCollection::DEFAULT_LIMITS[:max_lines] = 5000 ``` -File diff will be suppressed (technically different from collapsed, but behaves the same, and is expandable) if it has more than 5000 lines. +File diff is suppressed (technically different from collapsed, but behaves the same, and is expandable) if it has more than 5000 lines. -This limit is currently hardcoded and only applied on GitLab. +This limit is hardcoded and only applied on GitLab. ## Viewers diff --git a/doc/development/fe_guide/droplab/droplab.md b/doc/development/fe_guide/droplab/droplab.md index 85d94d7e588..8f1ecc115fe 100644 --- a/doc/development/fe_guide/droplab/droplab.md +++ b/doc/development/fe_guide/droplab/droplab.md @@ -22,7 +22,7 @@ The value is irrelevant. The DropLab class has no side effects, so you must always call `.init` when the DOM is ready. `DropLab.prototype.init` takes the same arguments as `DropLab.prototype.addHook`. -If you don't provide any arguments, it will globally query and instantiate all +If you don't provide any arguments, it globally queries and instantiates all DropLab-compatible dropdowns. ```html @@ -103,7 +103,7 @@ droplab.addHook(trigger, list); ### Dynamic data -Adding `data-dynamic` to your dropdown element will enable dynamic list +Adding `data-dynamic` to your dropdown element enables dynamic list rendering. You can template a list item using the keys of the data object provided. Use the @@ -111,7 +111,7 @@ handlebars syntax `{{ value }}` to HTML escape the value. Use the `<%= value %>` syntax to interpolate the value. Use the `<%= value %>` syntax to evaluate the value. -Passing an array of objects to `DropLab.prototype.addData` will render that data +Passing an array of objects to `DropLab.prototype.addData` renders that data for all `data-dynamic` dropdown lists tracked by that DropLab instance. ```html @@ -227,8 +227,8 @@ provides some potentially useful data. Plugins are objects that are registered to be executed when a hook is added (when a DropLab trigger and dropdown are instantiated). -If no modules API is detected, the library will fall back as it does with -`window.DropLab` and will add `window.DropLab.plugins.PluginName`. +If no modules API is detected, the library falls back as it does with +`window.DropLab` and adds `window.DropLab.plugins.PluginName`. ### Usage diff --git a/doc/development/fe_guide/droplab/plugins/filter.md b/doc/development/fe_guide/droplab/plugins/filter.md index 8bfd0d2f5c9..79f10cdb6c1 100644 --- a/doc/development/fe_guide/droplab/plugins/filter.md +++ b/doc/development/fe_guide/droplab/plugins/filter.md @@ -49,7 +49,7 @@ droplab.addData('trigger', [{ In the previous code, the input string is compared against the `test` key of the passed data objects. -Optionally you can set `filterFunction` to a function. This function will be +Optionally you can set `filterFunction` to a function. This function is then used instead of `Filter`'s built-in string search. `filterFunction` is passed two arguments: the first is one of the data objects, and the second is the current input value. diff --git a/doc/development/fe_guide/droplab/plugins/input_setter.md b/doc/development/fe_guide/droplab/plugins/input_setter.md index dbef79e1cbb..a3c073520cb 100644 --- a/doc/development/fe_guide/droplab/plugins/input_setter.md +++ b/doc/development/fe_guide/droplab/plugins/input_setter.md @@ -67,6 +67,6 @@ element's `data-selected-id` to `1`. Optionally, you can set `inputAttribute` to a string that's the name of an attribute on your `input` element that you want to update. If you don't provide -an `inputAttribute`, `InputSetter` will update the `value` of the `input` +an `inputAttribute`, `InputSetter` updates the `value` of the `input` element if it's an `INPUT` element, or the `textContent` of the `input` element if it isn't an `INPUT` element. diff --git a/doc/development/fe_guide/frontend_faq.md b/doc/development/fe_guide/frontend_faq.md index b02a221c959..d239e5404ea 100644 --- a/doc/development/fe_guide/frontend_faq.md +++ b/doc/development/fe_guide/frontend_faq.md @@ -82,13 +82,13 @@ follow up issue and attach it to the component implementation epic found within ### 4. My submit form button becomes disabled after submitting -If you are using a submit button inside a form and you attach an `onSubmit` event listener on the form element, [this piece of code](https://gitlab.com/gitlab-org/gitlab/blob/794c247a910e2759ce9b401356432a38a4535d49/app/assets/javascripts/main.js#L225) will add a `disabled` class selector to the submit button when the form is submitted. +If you are using a submit button inside a form and you attach an `onSubmit` event listener on the form element, [this piece of code](https://gitlab.com/gitlab-org/gitlab/blob/794c247a910e2759ce9b401356432a38a4535d49/app/assets/javascripts/main.js#L225) adds a `disabled` class selector to the submit button when the form is submitted. To avoid this behavior, add the class `js-no-auto-disable` to the button. ### 5. Should I use a full URL (i.e. `gon.gitlab_url`) or a full path (i.e. `gon.relative_url_root`) when referencing backend endpoints? -It's preferred to use a **full path** over a **full URL** because the URL will use the hostname configured with -GitLab which may not match the request. This will cause [CORS issues like this Web IDE one](https://gitlab.com/gitlab-org/gitlab/-/issues/36810). +It's preferred to use a **full path** over a **full URL** because the URL uses the hostname configured with +GitLab which may not match the request. This causes [CORS issues like this Web IDE one](https://gitlab.com/gitlab-org/gitlab/-/issues/36810). Example: @@ -161,8 +161,9 @@ Sometimes it's necessary to test locally what the frontend production build woul 1. Open `gitlab.yaml` located in your `gitlab` installation folder, scroll down to the `webpack` section and change `dev_server` to `enabled: false`. 1. Run `yarn webpack-prod && gdk restart rails-web`. -The production build takes a few minutes to be completed; any code change at this point will be +The production build takes a few minutes to be completed; any code changes at this point are displayed only after executing the item 3 above again. + To return to the normal development mode: 1. Open `gitlab.yaml` located in your `gitlab` installation folder, scroll down to the `webpack` section and change back `dev_server` to `enabled: true`. diff --git a/doc/development/fe_guide/icons.md b/doc/development/fe_guide/icons.md index cdfb9c2040f..1468e886220 100644 --- a/doc/development/fe_guide/icons.md +++ b/doc/development/fe_guide/icons.md @@ -31,7 +31,7 @@ sprite_icon(icon_name, size: nil, css_class: '') - **icon_name**: Use the icon_name for the SVG sprite in the list of ([GitLab SVGs](https://gitlab-org.gitlab.io/gitlab-svgs)). - **size (optional)**: Use one of the following sizes : 16, 24, 32, 48, 72 (this - will be translated into a `s16` class) + is translated into a `s16` class) - **css_class (optional)**: If you want to add additional CSS classes. **Example** diff --git a/doc/development/fe_guide/principles.md b/doc/development/fe_guide/principles.md index 20ce657cebc..4952d023d90 100644 --- a/doc/development/fe_guide/principles.md +++ b/doc/development/fe_guide/principles.md @@ -6,7 +6,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Principles -These principles will ensure that your frontend contribution starts off in the right direction. +These principles ensure that your frontend contribution starts off in the right direction. ## Discuss architecture before implementation @@ -14,7 +14,7 @@ Discuss your architecture design in an issue before writing code. This helps dec ## Be consistent -There are multiple ways of writing code to accomplish the same results. We should be as consistent as possible in how we write code across our codebases. This will make it easier for us to maintain our code across GitLab. +There are multiple ways of writing code to accomplish the same results. We should be as consistent as possible in how we write code across our codebases. This makes it easier for us to maintain our code across GitLab. ## Improve code [iteratively](https://about.gitlab.com/handbook/values/#iteration) diff --git a/doc/development/fe_guide/style/javascript.md b/doc/development/fe_guide/style/javascript.md index d404d3e02e9..8e3538e891d 100644 --- a/doc/development/fe_guide/style/javascript.md +++ b/doc/development/fe_guide/style/javascript.md @@ -19,7 +19,7 @@ You can run eslint locally by running `yarn eslint` ## Avoid forEach Avoid forEach when mutating data. Use `map`, `reduce` or `filter` instead of `forEach` -when mutating data. This will minimize mutations in functions, +when mutating data. This minimizes mutations in functions, which aligns with [Airbnb's style guide](https://github.com/airbnb/javascript#testing--for-real). ```javascript @@ -237,7 +237,7 @@ document.addEventListener("DOMContentLoaded", function(event) { ### Avoid side effects in constructors Avoid making asynchronous calls, API requests or DOM manipulations in the `constructor`. -Move them into separate functions instead. This will make tests easier to write and +Move them into separate functions instead. This makes tests easier to write and avoids violating the [Single Responsibility Principle](https://en.wikipedia.org/wiki/Single_responsibility_principle). ```javascript diff --git a/doc/development/fe_guide/style/scss.md b/doc/development/fe_guide/style/scss.md index 6b2b83e392a..a4cae12c4f3 100644 --- a/doc/development/fe_guide/style/scss.md +++ b/doc/development/fe_guide/style/scss.md @@ -12,7 +12,7 @@ easy to maintain, and performant for the end-user. ## Rules -Our CSS is a mixture of current and legacy approaches. That means sometimes it may be difficult to follow this guide to the letter; it means you will definitely run into exceptions, where following the guide is difficult to impossible without outsized effort. In those cases, you may work with your reviewers and maintainers to identify an approach that does not fit these rules. Please endeavor to limit these cases. +Our CSS is a mixture of current and legacy approaches. That means sometimes it may be difficult to follow this guide to the letter; it means you are likely to run into exceptions, where following the guide is difficult to impossible without outsized effort. In those cases, you may work with your reviewers and maintainers to identify an approach that does not fit these rules. Please endeavor to limit these cases. ### Utility Classes @@ -132,8 +132,8 @@ We use [SCSS Lint](https://github.com/sds/scss-lint) to check for style guide co ruleset in `.scss-lint.yml`, which is located in the home directory of the project. -To check if any warnings will be produced by your changes, you can run `rake -scss_lint` in the GitLab directory. SCSS Lint will also run in GitLab CI/CD to +To check if any warnings are produced by your changes, run `rake +scss_lint` in the GitLab directory. SCSS Lint also runs in GitLab CI/CD to catch any warnings. If the Rake task is throwing warnings you don't understand, SCSS Lint's @@ -147,4 +147,4 @@ the SCSS style guide, you can use [CSSComb](https://github.com/csscomb/csscomb.j CSSComb globally (system-wide). Run it in the GitLab directory with `csscomb app/assets/stylesheets` to automatically fix issues with CSS/SCSS. -Note that this won't fix every problem, but it should fix a majority. +Note that this doesn't fix every problem, but it should fix a majority. diff --git a/doc/development/fe_guide/style/vue.md b/doc/development/fe_guide/style/vue.md index 5825ffdf65f..b85c1b1de35 100644 --- a/doc/development/fe_guide/style/vue.md +++ b/doc/development/fe_guide/style/vue.md @@ -98,7 +98,7 @@ Please check this [rules](https://github.com/vuejs/eslint-plugin-vue#bulb-rules) We discourage the use of the spread operator in this specific case in order to keep our codebase explicit, discoverable, and searchable. - This applies in any place where we'll benefit from the above, such as + This applies in any place where we would benefit from the above, such as when [initializing Vuex state](../vuex.md#why-not-just-spread-the-initial-state). The pattern above also enables us to easily parse non scalar values during instantiation. @@ -667,7 +667,7 @@ The goal of this accord is to make sure we are all on the same page. 1. If you need to grab data from the DOM, you may query the DOM 1 time while bootstrapping your application to grab data attributes using `dataset`. You can do this without jQuery. 1. You may use a jQuery dependency in Vue.js following [this example from the docs](https://vuejs.org/v2/examples/select2.html). 1. If an outside jQuery Event needs to be listen to inside the Vue application, you may use jQuery event listeners. - 1. We will avoid adding new jQuery events when they are not required. Instead of adding new jQuery events take a look at [different methods to do the same task](https://vuejs.org/v2/api/#vm-emit). + 1. We avoid adding new jQuery events when they are not required. Instead of adding new jQuery events take a look at [different methods to do the same task](https://vuejs.org/v2/api/#vm-emit). 1. You may query the `window` object one time, while bootstrapping your application for application specific data (e.g. `scrollTo` is ok to access anytime). Do this access during the bootstrapping of your application. 1. You may have a temporary but immediate need to create technical debt by writing code that does not follow our standards, to be refactored later. Maintainers need to be ok with the tech debt in the first place. An issue should be created for that tech debt to evaluate it further and discuss. In the coming months you should fix that tech debt, with its priority to be determined by maintainers. 1. When creating tech debt you must write the tests for that code before hand and those tests may not be rewritten. e.g. jQuery tests rewritten to Vue tests. diff --git a/doc/development/fe_guide/tooling.md b/doc/development/fe_guide/tooling.md index dac47bcf158..d33022b9355 100644 --- a/doc/development/fe_guide/tooling.md +++ b/doc/development/fe_guide/tooling.md @@ -14,21 +14,21 @@ We use ESLint to encapsulate and enforce frontend code standards. Our configurat This section describes yarn scripts that are available to validate and apply automatic fixes to files using ESLint. -To check all currently staged files (based on `git diff`) with ESLint, run the following script: +To check all staged files (based on `git diff`) with ESLint, run the following script: ```shell yarn eslint-staged ``` -A list of problems found will be logged to the console. +A list of problems found are logged to the console. -To apply automatic ESLint fixes to all currently staged files (based on `git diff`), run the following script: +To apply automatic ESLint fixes to all staged files (based on `git diff`), run the following script: ```shell yarn eslint-staged-fix ``` -If manual changes are required, a list of changes will be sent to the console. +If manual changes are required, a list of changes are sent to the console. To check **all** files in the repository with ESLint, run the following script: @@ -36,7 +36,7 @@ To check **all** files in the repository with ESLint, run the following script: yarn eslint ``` -A list of problems found will be logged to the console. +A list of problems found are logged to the console. To apply automatic ESLint fixes to **all** files in the repository, run the following script: @@ -44,7 +44,7 @@ To apply automatic ESLint fixes to **all** files in the repository, run the foll yarn eslint-fix ``` -If manual changes are required, a list of changes will be sent to the console. +If manual changes are required, a list of changes are sent to the console. WARNING: Limit use to global rule updates. Otherwise, the changes can lead to huge Merge Requests. @@ -156,13 +156,13 @@ The source of these Yarn scripts can be found in `/scripts/frontend/prettier.js` node ./scripts/frontend/prettier.js check-all ./vendor/ ``` -This will go over all files in a specific folder check it. +This iterates over all files in a specific folder, and checks them. ```shell node ./scripts/frontend/prettier.js save-all ./vendor/ ``` -This will go over all files in a specific folder and save it. +This iterates over all files in a specific folder and saves them. ### VSCode Settings diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md index e0a8c5ffa70..41fbd128631 100644 --- a/doc/development/fe_guide/vue.md +++ b/doc/development/fe_guide/vue.md @@ -62,11 +62,11 @@ Be sure to read about [page-specific JavaScript](performance.md#page-specific-ja While mounting a Vue application, you might need to provide data from Rails to JavaScript. To do that, you can use the `data` attributes in the HTML element and query them while mounting the application. -You should only do this while initializing the application, because the mounted element will be replaced with Vue-generated DOM. +You should only do this while initializing the application, because the mounted element is replaced with a Vue-generated DOM. The advantage of providing data from the DOM to the Vue instance through `props` in the `render` function instead of querying the DOM inside the main Vue component is avoiding the need to create a fixture or an HTML element in the unit test, -which will make the tests easier. +which makes the tests easier. See the following example, also, please refer to our [Vue style guide](style/vue.md#basic-rules) for additional information on why we explicitly declare the data being passed into the Vue app; @@ -98,8 +98,8 @@ return new Vue({ #### Accessing the `gl` object -When we need to query the `gl` object for data that won't change during the application's life cycle, we should do it in the same place where we query the DOM. -By following this practice, we can avoid the need to mock the `gl` object, which will make tests easier. +When we need to query the `gl` object for data that doesn't change during the application's life cycle, we should do it in the same place where we query the DOM. +By following this practice, we can avoid the need to mock the `gl` object, which makes tests easier. It should be done while initializing our Vue instance, and the data should be provided as `props` to the main component: ```javascript @@ -162,12 +162,12 @@ This approach has a few benefits: ### A folder for Components -This folder holds all components that are specific of this new feature. -If you need to use or create a component that will probably be used somewhere +This folder holds all components that are specific to this new feature. +If you need to use or create a component that is likely to be used somewhere else, please refer to `vue_shared/components`. A good rule of thumb to know when you should create a component is to think if -it will be reusable elsewhere. +it could be reusable elsewhere. For example, tables are used in a quite amount of places across GitLab, a table would be a good fit for a component. On the other hand, a table cell used only @@ -192,7 +192,7 @@ Check this [page](vuex.md) for more details. In the [Vue documentation](https://vuejs.org/v2/api/#Options-Data) the Data function/object is defined as follows: -> The data object for the Vue instance. Vue will recursively convert its properties into getter/setters to make it “reactive”. The object must be plain: native objects such as browser API objects and prototype properties are ignored. A rule of thumb is that data should just be data - it is not recommended to observe objects with their own stateful behavior. +> The data object for the Vue instance. Vue recursively converts its properties into getter/setters to make it “reactive”. The object must be plain: native objects such as browser API objects and prototype properties are ignored. A rule of thumb is that data should just be data - it is not recommended to observe objects with their own stateful behavior. Based on the Vue guidance: diff --git a/doc/development/fe_guide/vue3_migration.md b/doc/development/fe_guide/vue3_migration.md index 7fb4e039609..9d2f3b27968 100644 --- a/doc/development/fe_guide/vue3_migration.md +++ b/doc/development/fe_guide/vue3_migration.md @@ -82,7 +82,7 @@ const FunctionalComp = (props, slots) => { } ``` -It is not recommended to replace stateful components with functional components unless you absolutely need a performance improvement right now. In Vue 3, performance gains for functional components will be negligible. +It is not recommended to replace stateful components with functional components unless you absolutely need a performance improvement right now. In Vue 3, performance gains for functional components are negligible. ## Old slots syntax with `slot` attribute diff --git a/doc/development/module_with_instance_variables.md b/doc/development/module_with_instance_variables.md index 66b954df268..75575105178 100644 --- a/doc/development/module_with_instance_variables.md +++ b/doc/development/module_with_instance_variables.md @@ -121,7 +121,7 @@ module Gitlab end ``` -Now the cop won't complain. Here's a bad example which we could rewrite: +Now the cop doesn't complain. Here's a bad example which we could rewrite: ``` ruby module SpamCheckService @@ -213,14 +213,14 @@ module M end ``` -Note that you need to enable it at some point, otherwise everything below -won't be checked. +Note that you need to enable it at some point, otherwise nothing below +that point is checked. ## Things we might need to ignore right now Because of the way Rails helpers and mailers work, we might not be able to avoid the use of instance variables there. For those cases, we could ignore -them at the moment. At least we're not going to share those modules with +them at the moment. Those modules are not shared with other random objects, so they're still somewhat isolated. ## Instance variables in views diff --git a/doc/development/multi_version_compatibility.md b/doc/development/multi_version_compatibility.md index 1e35098dbfc..5d5cbb2fe8f 100644 --- a/doc/development/multi_version_compatibility.md +++ b/doc/development/multi_version_compatibility.md @@ -21,7 +21,7 @@ We must make sure that the application works properly in these states. For GitLab.com, we also run a set of canary servers which run a more recent version of the application. Users with the canary cookie set would be handled by these servers. Some URL patterns may also be forced to the canary servers, even without the cookie being set. This also means that some pages may match the pattern and get handled by canary servers, -but AJAX requests to URLs (like the GraphQL endpoint) won't match the pattern. +but AJAX requests to URLs (like the GraphQL endpoint) fail to match the pattern. With this canary setup, we'd be in this mixed-versions state for an extended period of time until canary is promoted to production and post-deployment migrations run. @@ -38,7 +38,7 @@ default. The feature flag can be enabled when the deployment is in a consistent state. However, this method of synchronization doesn't guarantee that customers with on-premise instances can [upgrade with zero downtime](https://docs.gitlab.com/omnibus/update/#zero-downtime-updates) -since point releases bundle many changes together. Minimizing the time +because point releases bundle many changes together. Minimizing the time between when versions are out of sync across the fleet may help mitigate errors caused by upgrades. @@ -70,7 +70,7 @@ This type of change may look like an immediate switch between the two implementa especially with the canary stage, there is an extended period of time where both version of the code coexists in production. -1. **expand**: a new route is added, pointing to the same controller as the old one. But nothing in the application will generate links for the new routes. +1. **expand**: a new route is added, pointing to the same controller as the old one. But nothing in the application generates links for the new routes. 1. **migrate**: now that every machine in the fleet can understand the new route, we can generate links with the new routing. 1. **contract**: the old route can be safely removed. (If the old route was likely to be widely shared, like the link to a repository file, we might want to add redirects and keep the old route for a longer period.) @@ -84,7 +84,7 @@ When we need to add a new parameter to a Sidekiq worker class, we can split this 1. **migrate**: we add the new parameter to all the invocations of the worker. 1. **contract**: we remove the default value. -At a first look, it may seem safe to bundle expand and migrate into a single milestone, but this will cause an outage if Puma restarts before Sidekiq. +At a first look, it may seem safe to bundle expand and migrate into a single milestone, but this causes an outage if Puma restarts before Sidekiq. Puma enqueues jobs with an extra parameter that the old Sidekiq cannot handle. ### Database migrations @@ -134,12 +134,12 @@ And these deployments align perfectly with application changes. With all those details in mind, let's imagine we need to replace a query, and this query has an index to support it. -1. **expand**: this is the from `Schema A` to `Schema B` deployment. We add the new index, but the application will ignore it for now -1. **migrate**: this is the `Version N` to `Version N+1` application deployment. The new code is deployed, at this point in time only the new query will run. +1. **expand**: this is the from `Schema A` to `Schema B` deployment. We add the new index, but the application ignores it for now. +1. **migrate**: this is the `Version N` to `Version N+1` application deployment. The new code is deployed, at this point in time only the new query runs. 1. **contract**: from `Schema B` to `Schema C` (post-deployment migration). Nothing uses the old index anymore, we can safely remove it. -This is only an example. More complex migrations, especially when background migrations are needed will -still require more than one milestone. For details please refer to our [migration style guide](migration_style_guide.md). +This is only an example. More complex migrations, especially when background migrations are needed may +require more than one milestone. For details please refer to our [migration style guide](migration_style_guide.md). ## Examples of previous incidents diff --git a/doc/development/query_recorder.md b/doc/development/query_recorder.md index b2414802180..0644c0a62ed 100644 --- a/doc/development/query_recorder.md +++ b/doc/development/query_recorder.md @@ -30,7 +30,7 @@ In some cases the query count might change slightly between runs for unrelated r ## Cached queries -By default, QueryRecorder will ignore [cached queries](merge_request_performance_guidelines.md#cached-queries) in the count. However, it may be better to count +By default, QueryRecorder ignores [cached queries](merge_request_performance_guidelines.md#cached-queries) in the count. However, it may be better to count all queries to avoid introducing an N+1 query that may be masked by the statement cache. To do this, this requires the `:use_sql_query_cache` flag to be set. You should pass the `skip_cached` variable to `QueryRecorder` and use the `exceed_all_query_limit` matcher: @@ -73,7 +73,7 @@ There are multiple ways to find the source of queries. - View the call backtrace for the specific `QueryRecorder` instance you want by using `ActiveRecord::QueryRecorder.new(query_recorder_debug: true)`. The output - will be in `test.log` + is stored in file `test.log`. - Enable the call backtrace for all tests using the `QUERY_RECORDER_DEBUG` environment variable. @@ -83,7 +83,7 @@ There are multiple ways to find the source of queries. QUERY_RECORDER_DEBUG=1 bundle exec rspec spec/requests/api/projects_spec.rb ``` - This will log calls to QueryRecorder into the `test.log` file. For example: + This logs calls to QueryRecorder into the `test.log` file. For example: ```sql QueryRecorder SQL: SELECT COUNT(*) FROM "issues" WHERE "issues"."deleted_at" IS NULL AND "issues"."project_id" = $1 AND ("issues"."state" IN ('opened')) AND "issues"."confidential" = $2 diff --git a/doc/development/reactive_caching.md b/doc/development/reactive_caching.md index fdf16358614..5d514ffbfc9 100644 --- a/doc/development/reactive_caching.md +++ b/doc/development/reactive_caching.md @@ -138,7 +138,7 @@ the model/service. - `with_reactive_cache` must be called where the result of `calculate_reactive_cache` is required. - A block can be given to `with_reactive_cache`. `with_reactive_cache` can also take - any number of arguments. Any arguments passed to `with_reactive_cache` will be + any number of arguments. Any arguments passed to `with_reactive_cache` are passed to `calculate_reactive_cache`. The arguments passed to `with_reactive_cache` are appended to the cache key name. - If `with_reactive_cache` is called when the result has already been cached, the diff --git a/doc/development/testing_guide/ci.md b/doc/development/testing_guide/ci.md index 35ad632866c..7318f767219 100644 --- a/doc/development/testing_guide/ci.md +++ b/doc/development/testing_guide/ci.md @@ -27,7 +27,7 @@ Our current CI parallelization setup is as follows: `knapsack/rspec*_pg_*.json` files and merge them all together into a single `knapsack/report-master.json` file that is saved as artifact. -After that, the next pipeline will use the up-to-date `knapsack/report-master.json` file. +After that, the next pipeline uses the up-to-date `knapsack/report-master.json` file. ## Monitoring diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md index d0a1ef8aada..4f30023dc56 100644 --- a/doc/development/testing_guide/review_apps.md +++ b/doc/development/testing_guide/review_apps.md @@ -164,7 +164,7 @@ used by the `review-deploy` and `review-stop` jobs. You need to [open an access request (internal link)](https://gitlab.com/gitlab-com/access-requests/-/issues/new) for the `gcp-review-apps-dev` GCP group and role. -This will grant you the following permissions for: +This grants you the following permissions for: - [Retrieving pod logs](#dig-into-a-pods-logs). Granted by [Viewer (`roles/viewer`)](https://cloud.google.com/iam/docs/understanding-roles#kubernetes-engine-roles). - [Running a Rails console](#run-a-rails-console). Granted by [Kubernetes Engine Developer (`roles/container.pods.exec`)](https://cloud.google.com/iam/docs/understanding-roles#kubernetes-engine-roles). @@ -317,7 +317,7 @@ kubectl get cm --sort-by='{.metadata.creationTimestamp}' | grep 'review-' | grep ### Using K9s -[K9s](https://github.com/derailed/k9s) is a powerful command line dashboard which allows you to filter by labels. This can help identify trends with apps exceeding the [review-app resource requests](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/review_apps/base-config.yaml). Kubernetes will schedule pods to nodes based on resource requests and allow for CPU usage up to the limits. +[K9s](https://github.com/derailed/k9s) is a powerful command line dashboard which allows you to filter by labels. This can help identify trends with apps exceeding the [review-app resource requests](https://gitlab.com/gitlab-org/gitlab/-/blob/master/scripts/review_apps/base-config.yaml). Kubernetes schedules pods to nodes based on resource requests and allow for CPU usage up to the limits. - In K9s you can sort or add filters by typing the `/` character - `-lrelease=<review-app-slug>` - filters down to all pods for a release. This aids in determining what is having issues in a single deployment @@ -395,8 +395,8 @@ helm ls -d | grep "Jun 4" | cut -f1 | xargs helm delete --purge #### Mitigation steps taken to avoid this problem in the future -We've created a new node pool with smaller machines so that it's less likely -that a machine will hit the "too many mount points" problem in the future. +We've created a new node pool with smaller machines to reduce the risk +that a machine reaches the "too many mount points" problem in the future. ## Frequently Asked Questions diff --git a/doc/development/testing_guide/smoke.md b/doc/development/testing_guide/smoke.md index 1b4604a71a0..4fd2729a4d3 100644 --- a/doc/development/testing_guide/smoke.md +++ b/doc/development/testing_guide/smoke.md @@ -7,11 +7,11 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Smoke Tests It is imperative in any testing suite that we have Smoke Tests. In short, smoke -tests will run quick sanity end-to-end functional tests from GitLab QA and are +tests run quick end-to-end functional tests from GitLab QA and are designed to run against the specified environment to ensure that basic functionality is working. -Currently, our suite consists of this basic functionality coverage: +Our suite consists of this basic functionality coverage: - User standard authentication - SSH Key creation and addition to a user diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md index 01e460929a8..14d4ee82f75 100644 --- a/doc/development/testing_guide/testing_levels.md +++ b/doc/development/testing_guide/testing_levels.md @@ -129,14 +129,14 @@ graph RL - **All server requests**: When running frontend unit tests, the backend may not be reachable, so all outgoing requests need to be mocked. - **Asynchronous background operations**: - Background operations cannot be stopped or waited on, so they will continue running in the following tests and cause side effects. + Background operations cannot be stopped or waited on, so they continue running in the following tests and cause side effects. #### What *not* to mock in unit tests - **Non-exported functions or classes**: - Everything that is not exported can be considered private to the module, and will be implicitly tested through the exported classes and functions. + Everything that is not exported can be considered private to the module, and is implicitly tested through the exported classes and functions. - **Methods of the class under test**: - By mocking methods of the class under test, the mocks will be tested and not the real methods. + By mocking methods of the class under test, the mocks are tested and not the real methods. - **Utility functions (pure functions, or those that only modify parameters)**: If a function has no side effects because it has no state, it is safe to not mock it in tests. - **Full HTML pages**: @@ -206,7 +206,7 @@ graph RL - **All server requests**: Similar to unit tests, when running component tests, the backend may not be reachable, so all outgoing requests need to be mocked. - **Asynchronous background operations**: - Similar to unit tests, background operations cannot be stopped or waited on. This means they will continue running in the following tests and cause side effects. + Similar to unit tests, background operations cannot be stopped or waited on. This means they continue running in the following tests and cause side effects. - **Child components**: Every component is tested individually, so child components are mocked. See also [`shallowMount()`](https://vue-test-utils.vuejs.org/api/#shallowmount) @@ -214,7 +214,7 @@ graph RL #### What *not* to mock in component tests - **Methods or computed properties of the component under test**: - By mocking part of the component under test, the mocks will be tested and not the real component. + By mocking part of the component under test, the mocks are tested and not the real component. - **Functions and classes independent from Vue**: All plain JavaScript code is already covered by unit tests and needs not to be mocked in component tests. @@ -295,7 +295,7 @@ graph RL Similar to unit and component tests, when running component tests, the backend may not be reachable, so all outgoing requests must be mocked. - **Asynchronous background operations that are not perceivable on the page**: Background operations that affect the page must be tested on this level. - All other background operations cannot be stopped or waited on, so they will continue running in the following tests and cause side effects. + All other background operations cannot be stopped or waited on, so they continue running in the following tests and cause side effects. #### What *not* to mock in integration tests @@ -360,7 +360,7 @@ possible). | Tests path | Testing engine | Notes | | ---------- | -------------- | ----- | -| `spec/features/` | [Capybara](https://github.com/teamcapybara/capybara) + [RSpec](https://github.com/rspec/rspec-rails#feature-specs) | If your test has the `:js` metadata, the browser driver will be [Poltergeist](https://github.com/teamcapybara/capybara#poltergeist), otherwise it's using [RackTest](https://github.com/teamcapybara/capybara#racktest). | +| `spec/features/` | [Capybara](https://github.com/teamcapybara/capybara) + [RSpec](https://github.com/rspec/rspec-rails#feature-specs) | If your test has the `:js` metadata, the browser driver is [Poltergeist](https://github.com/teamcapybara/capybara#poltergeist), otherwise it's using [RackTest](https://github.com/teamcapybara/capybara#racktest). | ### Frontend feature tests @@ -453,13 +453,13 @@ should take care of not introducing too many (slow and duplicated) tests. The reasons why we should follow these best practices are as follows: -- System tests are slow to run since they spin up the entire application stack +- System tests are slow to run because they spin up the entire application stack in a headless browser, and even slower when they integrate a JS driver - When system tests run with a JavaScript driver, the tests are run in a different thread than the application. This means it does not share a - database connection and your test will have to commit the transactions in + database connection and your test must commit the transactions in order for the running application to see the data (and vice-versa). In that - case we need to truncate the database after each spec instead of simply + case we need to truncate the database after each spec instead of rolling back a transaction (the faster strategy that's in use for other kind of tests). This is slower than transactions, however, so we want to use truncation only when necessary. diff --git a/doc/development/testing_guide/testing_migrations_guide.md b/doc/development/testing_guide/testing_migrations_guide.md index 9844754cd97..31054d0ffb2 100644 --- a/doc/development/testing_guide/testing_migrations_guide.md +++ b/doc/development/testing_guide/testing_migrations_guide.md @@ -24,15 +24,15 @@ Adding a `:migration` tag to a test signature enables some custom RSpec [`spec/support/migration.rb`](https://gitlab.com/gitlab-org/gitlab/-/blob/f81fa6ab1dd788b70ef44b85aaba1f31ffafae7d/spec/support/migration.rb) to run. -A `before` hook will revert all migrations to the point that a migration +A `before` hook reverts all migrations to the point that a migration under test is not yet migrated. -In other words, our custom RSpec hooks will find a previous migration, and +In other words, our custom RSpec hooks finds a previous migration, and migrate the database **down** to the previous migration version. With this approach you can test a migration against a database schema. -An `after` hook will migrate the database **up** and reinstitute the latest +An `after` hook migrates the database **up** and reinstitutes the latest schema version, so that the process does not affect subsequent specs and ensures proper isolation. @@ -40,7 +40,7 @@ ensures proper isolation. To test an `ActiveRecord::Migration` class (i.e., a regular migration `db/migrate` or a post-migration `db/post_migrate`), you -will need to load the migration file by using the `require_migration!` helper +must load the migration file by using the `require_migration!` helper method because it is not autoloaded by Rails. Example: @@ -57,12 +57,12 @@ RSpec.describe ... #### `require_migration!` -Since the migration files are not autoloaded by Rails, you will need to manually +Since the migration files are not autoloaded by Rails, you must manually load the migration file. To do so, you can use the `require_migration!` helper method which can automatically load the correct migration file based on the spec filename. For example, if your spec file is named as `populate_foo_column_spec.rb` then the -helper method will try to load `${schema_version}_populate_foo_column.rb` migration file. +helper method tries to load `${schema_version}_populate_foo_column.rb` migration file. In case there is no pattern between your spec file and the actual migration file, you can provide the migration filename without the schema version, like so: @@ -85,8 +85,8 @@ project = table(:projects).create!(id: 1, name: 'gitlab1', path: 'gitlab1') #### `migrate!` -Use the `migrate!` helper to run the migration that is under test. It will not only -run the migration, but will also bump the schema version in the `schema_migrations` +Use the `migrate!` helper to run the migration that is under test. It +runs the migration and bumps the schema version in the `schema_migrations` table. It is necessary because in the `after` hook we trigger the rest of the migrations, and we need to know where to start. Example: @@ -103,7 +103,7 @@ end #### `reversible_migration` Use the `reversible_migration` helper to test migrations with either a -`change` or both `up` and `down` hooks. This will test that the state of +`change` or both `up` and `down` hooks. This tests that the state of the application and its data after the migration becomes reversed is the same as it was before the migration ran in the first place. The helper: @@ -184,7 +184,7 @@ end ## Testing a non-`ActiveRecord::Migration` class To test a non-`ActiveRecord::Migration` test (a background migration), -you will need to manually provide a required schema version. Please add a +you must manually provide a required schema version. Please add a `schema` tag to a context that you want to switch the database schema within. If not set, `schema` defaults to `:latest`. diff --git a/doc/development/utilities.md b/doc/development/utilities.md index f757adf3369..2d347df0559 100644 --- a/doc/development/utilities.md +++ b/doc/development/utilities.md @@ -100,7 +100,7 @@ Refer to [`override.rb`](https://gitlab.com/gitlab-org/gitlab/blob/master/lib/gi end ``` - Note that the check will only happen when either: + Note that the check only happens when either: - The overriding method is defined in a class, or: - The overriding method is defined in a module, and it's prepended to diff --git a/doc/development/windows.md b/doc/development/windows.md index c1c5f67e01b..6a7b2fa69ec 100644 --- a/doc/development/windows.md +++ b/doc/development/windows.md @@ -15,7 +15,7 @@ This is a guide for how to get a Windows development virtual machine on Google C Use of Microsoft Windows operating systems on company laptops is banned under GitLab's [Approved Operating Systems policy](https://about.gitlab.com/handbook/security/approved_os.html#windows). -This can make it difficult to develop features for the Windows platforms. Using GCP will allow us to have a temporary Windows machine that can be removed once we're done with it. +This can make it difficult to develop features for the Windows platforms. Using GCP allows us to have a temporary Windows machine that can be removed once we're done with it. ## Shared Windows runners @@ -74,7 +74,7 @@ Build a Google Cloud image with the above shared runners repository by doing the 1. Click **Set Windows password**. 1. Optional: Set a username or use default. 1. Click **Next**. -1. Copy and save the password as it won't be shown again. +1. Copy and save the password as it is not shown again. 1. Click **RDP** down arrow. 1. Click **Download the RDP file**. 1. Open the downloaded RDP file with the Windows remote desktop app (<https://docs.microsoft.com/en-us/windows-server/remote/remote-desktop-services/clients/remote-desktop-clients>). @@ -98,8 +98,8 @@ Here are a few tips on GCP and Windows. ### GCP cost savings -To minimise the cost of your GCP VM instance, stop it when you're not using it. -If you do, you'll need to re-download the RDP file from the console as the IP +To minimize the cost of your GCP VM instance, stop it when you're not using it. +If you do, you must download the RDP file again from the console as the IP address changes every time you stop and start it. ### chocolatey diff --git a/doc/subscriptions/self_managed/index.md b/doc/subscriptions/self_managed/index.md index 02dd609803d..a4affff92e4 100644 --- a/doc/subscriptions/self_managed/index.md +++ b/doc/subscriptions/self_managed/index.md @@ -45,13 +45,6 @@ billable user, with the following exceptions: - Members with Guest permissions on an Ultimate subscription. - GitLab-created service accounts: `Ghost User` and bots (`Support Bot`, [`Project bot users`](../../user/project/settings/project_access_tokens.md#project-bot-users), and so on). -### Users statistics - -To view a breakdown of the users within your instance, including active, billable, -and blocked, go to **Admin Area > Overview > Dashboard** and select **Users statistics** -in the **Users** section. For more details, see -[Users statistics](../../user/admin_area/index.md#users-statistics). - ### Tips for managing users and subscription seats Managing the number of users against the number of subscription seats can be a challenge: @@ -69,6 +62,7 @@ GitLab has several features which can help you manage the number of users: option. **Available in GitLab 13.6 and later**. - [Disable new sign-ups](../../user/admin_area/settings/sign_up_restrictions.md), and instead manage new users manually. +- View a breakdown of users by role in the [Users statistics](../../user/admin_area/index.md#users-statistics) page. ## Obtain a subscription @@ -86,13 +80,25 @@ instance, ensure you're purchasing enough seats to ## View your subscription -If you are an administrator, to view the status of your self-managed subscription, -log in to the your GitLab instance and go to the **License** page: +If you are an administrator, you can view the status of your subscription: 1. Go to **Admin Area**. 1. From the left-hand menu, select **License**. -Read more about the [license admin area](../../user/admin_area/license.md). +The **License** page includes the following details: + +- Licensee +- Plan +- When it was uploaded, started, and when it expires + +It also displays the following important statistics: + +| Field | Description | +|:-------------------|:------------| +| Users in License | The number of users you've paid for in the current license loaded on the system. This does not include the amount you've paid for `Users over license` during renewal. | +| Billable users | The daily count of billable users on your system. | +| Maximum users | The highest number of billable users on your system during the term of the loaded license. If this number exceeds your users in license count at any point, you incur users over license. | +| Users over license | The number of users that exceed the `Users in License` for the current license term. Charges for this number of users are incurred at the next renewal. | ## Renew your subscription @@ -116,9 +122,9 @@ the contact person who manages your subscription. It's important to regularly review your user accounts, because: -- A GitLab subscription is based on the number of users. You pay more than - you should if you renew for too many users, while the renewal fail if you - attempt to renew a subscription for too few users. +- A GitLab subscription is based on the number of users. You pay more than you should if you renew + for too many users, while the renewal fails if you attempt to renew a subscription for too few + users. - Stale user accounts can be a security risk. A regular review helps reduce this risk. #### Users over License @@ -129,6 +135,10 @@ count exceeds the number included in the subscription, known as the number of _users over license_, you must pay for the excess number of users either before renewal, or at the time of renewal. This is also known as the _true up_ process. +To view the number of _Users over License_ go to the **Admin Area**. + +### Add users to a subscription + Self-managed instances can add users to a subscription any time during the subscription period. The cost of additional users added during the subscription period is prorated from the date of purchase through the end of the subscription period. @@ -163,19 +173,6 @@ We recommend following these steps during renewal: 1. In the first box, enter the total number of user licenses you’ll need for the upcoming year. Be sure this number is at least **equal to, or greater than** the number of billable users in the system at the time of performing the renewal. 1. Enter the number of [users over license](#users-over-license) in the second box for the user overage incurred in your previous subscription term. - - NOTE: - You can find the _users over license_ in your instance's **Admin** dashboard by clicking on the **Admin Area** in the top bar, or navigating to `/admin`. - - The following table describes details of your admin dashboard and renewal terms: - - | Field | Description | - |:------|:------------| - | Users in License | The number of users you've paid for in the current license loaded on the system. This does not include the amount you've paid for `Users over license` during renewal. | - | Billable users | The daily count of billable users on your system. | - | Maximum users | The highest number of billable users on your system during the term of the loaded license. If this number exceeds your users in license count at any point, you incur users over license. | - | Users over license | The number of users that exceed the `Users in License` for the current license term. Charges for this number of users are incurred at the next renewal. | - 1. Review your renewal details and complete the payment process. 1. A license for the renewal term is available for download on the [Manage Purchases](https://customers.gitlab.com/subscriptions) page on the relevant subscription card. Select **Copy license to clipboard** or **Download license** to get a copy. 1. [Upload](../../user/admin_area/license.md#uploading-your-license) your new license to your instance. diff --git a/doc/user/application_security/threat_monitoring/index.md b/doc/user/application_security/threat_monitoring/index.md index 3552a7d5ffa..f7bd201aba9 100644 --- a/doc/user/application_security/threat_monitoring/index.md +++ b/doc/user/application_security/threat_monitoring/index.md @@ -101,7 +101,7 @@ reflected upon refresh. Enforcement status changes are deployed directly to a deployment namespace of the selected environment. By default, the network policy list contains predefined policies in a -disabled state. Once enabled,a predefined policy deploys to the +disabled state. Once enabled, a predefined policy deploys to the selected environment's deployment platform and you can manage it like the regular policies. diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md index 089ae9e05d1..e01934897d4 100644 --- a/doc/user/project/integrations/webhooks.md +++ b/doc/user/project/integrations/webhooks.md @@ -1360,6 +1360,37 @@ X-Gitlab-Event: Deployment Hook Note that `deployable_id` is the ID of the CI job. +### Member events + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/260347) in GitLab 13.7. + +Triggered when a user is added as a group member. + +**Request Header**: + +```plaintext +X-Gitlab-Event: Member Hook +``` + +**Request Body**: + +```json +{ + "created_at": "2020-12-11T04:57:22Z", + "updated_at": "2020-12-11T04:57:22Z", + "group_name": "webhook-test", + "group_path": "webhook-test", + "group_id": 100, + "user_username": "test_user", + "user_name": "Test User", + "user_email": "testuser@webhooktest.com", + "user_id": 64, + "group_access": "Guest", + "group_plan": null, + "event_name": "user_add_to_group" +} +``` + ### Feature Flag events Triggered when a feature flag is turned on or off. diff --git a/lib/gitlab/diff/file_collection/merge_request_diff_batch.rb b/lib/gitlab/diff/file_collection/merge_request_diff_batch.rb index 6ca98070fc3..64523f3b730 100644 --- a/lib/gitlab/diff/file_collection/merge_request_diff_batch.rb +++ b/lib/gitlab/diff/file_collection/merge_request_diff_batch.rb @@ -82,7 +82,7 @@ module Gitlab # rubocop: enable CodeReuse/ActiveRecord def batch_gradual_load? - Feature.enabled?(:diffs_gradual_load, @merge_request_diff.project) + Feature.enabled?(:diffs_gradual_load, @merge_request_diff.project, default_enabled: true) end end end diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb index 24c2d568d9a..ccd213fdffa 100644 --- a/spec/controllers/projects/cycle_analytics_controller_spec.rb +++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb @@ -32,41 +32,5 @@ RSpec.describe Projects::CycleAnalyticsController do end end - describe 'value stream analytics not set up flag' do - context 'with no data' do - it 'is true' do - get(:show, - params: { - namespace_id: project.namespace, - project_id: project - }) - - expect(response).to be_successful - expect(assigns(:cycle_analytics_no_data)).to eq(true) - end - end - - context 'with data' do - before do - issue = create(:issue, project: project, created_at: 4.days.ago) - milestone = create(:milestone, project: project, created_at: 5.days.ago) - issue.update(milestone: milestone) - - create_merge_request_closing_issue(user, project, issue) - end - - it 'is false' do - get(:show, - params: { - namespace_id: project.namespace, - project_id: project - }) - - expect(response).to be_successful - expect(assigns(:cycle_analytics_no_data)).to eq(false) - end - end - end - include_examples GracefulTimeoutHandling end diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb index be49ca16437..233a93c2054 100644 --- a/spec/features/cycle_analytics_spec.rb +++ b/spec/features/cycle_analytics_spec.rb @@ -24,10 +24,6 @@ RSpec.describe 'Value Stream Analytics', :js do wait_for_requests end - it 'shows introductory message' do - expect(page).to have_content('Introducing Value Stream Analytics') - end - it 'shows pipeline summary' do expect(new_issues_counter).to have_content('-') expect(commits_counter).to have_content('-') diff --git a/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js b/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js index 67f4bee766b..06b9385b112 100644 --- a/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js +++ b/spec/frontend/feature_flags/components/configure_feature_flags_modal_spec.js @@ -1,7 +1,6 @@ import { shallowMount } from '@vue/test-utils'; -import { GlModal, GlSprintf } from '@gitlab/ui'; +import { GlModal, GlSprintf, GlAlert } from '@gitlab/ui'; import Component from '~/feature_flags/components/configure_feature_flags_modal.vue'; -import Callout from '~/vue_shared/components/callout.vue'; describe('Configure Feature Flags Modal', () => { const mockEvent = { preventDefault: jest.fn() }; @@ -36,8 +35,8 @@ describe('Configure Feature Flags Modal', () => { const findGlModal = () => wrapper.find(GlModal); const findPrimaryAction = () => findGlModal().props('actionPrimary'); const findProjectNameInput = () => wrapper.find('#project_name_verification'); - const findDangerCallout = () => - wrapper.findAll(Callout).filter(c => c.props('category') === 'danger'); + const findDangerGlAlert = () => + wrapper.findAll(GlAlert).filter(c => c.props('variant') === 'danger'); describe('idle', () => { afterEach(() => wrapper.destroy()); @@ -86,10 +85,10 @@ describe('Configure Feature Flags Modal', () => { ); }); - it('should display one and only one danger callout', () => { - const dangerCallout = findDangerCallout(); - expect(dangerCallout.length).toBe(1); - expect(dangerCallout.at(0).props('message')).toMatch(/Regenerating the instance ID/); + it('should display one and only one danger alert', () => { + const dangerGlAlert = findDangerGlAlert(); + expect(dangerGlAlert.length).toBe(1); + expect(dangerGlAlert.at(0).text()).toMatch(/Regenerating the instance ID/); }); it('should display a message asking to fill the project name', () => { @@ -130,7 +129,7 @@ describe('Configure Feature Flags Modal', () => { }); it('should not display regenerating instance ID', async () => { - expect(findDangerCallout().exists()).toBe(false); + expect(findDangerGlAlert().exists()).toBe(false); }); it('should disable the project name input', async () => { diff --git a/spec/frontend/issuable_show/components/issuable_body_spec.js b/spec/frontend/issuable_show/components/issuable_body_spec.js index 0e4475e8103..5708eaf4a31 100644 --- a/spec/frontend/issuable_show/components/issuable_body_spec.js +++ b/spec/frontend/issuable_show/components/issuable_body_spec.js @@ -135,6 +135,33 @@ describe('IssuableBody', () => { expect(wrapper.emitted('edit-issuable')).toBeTruthy(); }); + + it.each(['keydown-title', 'keydown-description'])( + 'component emits `%s` event with event object and issuableMeta params via issuable-edit-form', + async eventName => { + const eventObj = { + preventDefault: jest.fn(), + stopPropagation: jest.fn(), + }; + const issuableMeta = { + issuableTitle: 'foo', + issuableDescription: 'foobar', + }; + + wrapper.setProps({ + editFormVisible: true, + }); + + await wrapper.vm.$nextTick(); + + const issuableEditForm = wrapper.find(IssuableEditForm); + + issuableEditForm.vm.$emit(eventName, eventObj, issuableMeta); + + expect(wrapper.emitted(eventName)).toBeTruthy(); + expect(wrapper.emitted(eventName)[0]).toMatchObject([eventObj, issuableMeta]); + }, + ); }); }); }); diff --git a/spec/frontend/issuable_show/components/issuable_edit_form_spec.js b/spec/frontend/issuable_show/components/issuable_edit_form_spec.js index 352e66cdffe..a865bdb5608 100644 --- a/spec/frontend/issuable_show/components/issuable_edit_form_spec.js +++ b/spec/frontend/issuable_show/components/issuable_edit_form_spec.js @@ -41,6 +41,40 @@ describe('IssuableEditForm', () => { wrapper.destroy(); }); + describe('watch', () => { + describe('issuable', () => { + it('sets title and description to `issuable.title` and `issuable.description` when those values are available', async () => { + wrapper.setProps({ + issuable: { + ...issuableEditFormProps.issuable, + title: 'Foo', + description: 'Foobar', + }, + }); + + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.title).toBe('Foo'); + expect(wrapper.vm.description).toBe('Foobar'); + }); + + it('sets title and description to empty string when `issuable.title` and `issuable.description` is unavailable', async () => { + wrapper.setProps({ + issuable: { + ...issuableEditFormProps.issuable, + title: null, + description: null, + }, + }); + + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.title).toBe(''); + expect(wrapper.vm.description).toBe(''); + }); + }); + }); + describe('created', () => { it('binds `update.issuable` and `close.form` event listeners', () => { const eventOnSpy = jest.spyOn(IssuableEventHub, '$on'); @@ -118,5 +152,42 @@ describe('IssuableEditForm', () => { expect(actionsEl.find('button.js-save').exists()).toBe(true); expect(actionsEl.find('button.js-cancel').exists()).toBe(true); }); + + describe('events', () => { + const eventObj = { + preventDefault: jest.fn(), + stopPropagation: jest.fn(), + }; + + it('component emits `keydown-title` event with event object and issuableMeta params via gl-form-input', async () => { + const titleInputEl = wrapper.find(GlFormInput); + + titleInputEl.vm.$emit('keydown', eventObj, 'title'); + + expect(wrapper.emitted('keydown-title')).toBeTruthy(); + expect(wrapper.emitted('keydown-title')[0]).toMatchObject([ + eventObj, + { + issuableTitle: wrapper.vm.title, + issuableDescription: wrapper.vm.description, + }, + ]); + }); + + it('component emits `keydown-description` event with event object and issuableMeta params via textarea', async () => { + const descriptionInputEl = wrapper.find('[data-testid="description"] textarea'); + + descriptionInputEl.trigger('keydown', eventObj, 'description'); + + expect(wrapper.emitted('keydown-description')).toBeTruthy(); + expect(wrapper.emitted('keydown-description')[0]).toMatchObject([ + eventObj, + { + issuableTitle: wrapper.vm.title, + issuableDescription: wrapper.vm.description, + }, + ]); + }); + }); }); }); diff --git a/spec/frontend/issuable_show/components/issuable_show_root_spec.js b/spec/frontend/issuable_show/components/issuable_show_root_spec.js index 112e4ccd340..ca0aefc1083 100644 --- a/spec/frontend/issuable_show/components/issuable_show_root_spec.js +++ b/spec/frontend/issuable_show/components/issuable_show_root_spec.js @@ -118,6 +118,27 @@ describe('IssuableShowRoot', () => { expect(wrapper.emitted('sidebar-toggle')).toBeTruthy(); }); + + it.each(['keydown-title', 'keydown-description'])( + 'component emits `%s` event with event object and issuableMeta params via issuable-body', + eventName => { + const eventObj = { + preventDefault: jest.fn(), + stopPropagation: jest.fn(), + }; + const issuableMeta = { + issuableTitle: 'foo', + issuableDescription: 'foobar', + }; + + const issuableBody = wrapper.find(IssuableBody); + + issuableBody.vm.$emit(eventName, eventObj, issuableMeta); + + expect(wrapper.emitted(eventName)).toBeTruthy(); + expect(wrapper.emitted(eventName)[0]).toMatchObject([eventObj, issuableMeta]); + }, + ); }); }); }); diff --git a/spec/frontend/issuable_show/mock_data.js b/spec/frontend/issuable_show/mock_data.js index 14e5febdc6b..af854f420bc 100644 --- a/spec/frontend/issuable_show/mock_data.js +++ b/spec/frontend/issuable_show/mock_data.js @@ -28,7 +28,9 @@ export const mockIssuableShowProps = { descriptionPreviewPath: '/gitlab-org/gitlab-shell/preview_markdown', editFormVisible: false, enableAutocomplete: true, + enableAutosave: true, enableEdit: true, + showFieldTitle: false, statusBadgeClass: 'status-box-open', statusIcon: 'issue-open-m', }; diff --git a/spec/frontend/pipelines/graph/graph_component_spec.js b/spec/frontend/pipelines/graph/graph_component_spec.js index 0ee8e21d8d7..7572dd83798 100644 --- a/spec/frontend/pipelines/graph/graph_component_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_spec.js @@ -44,6 +44,18 @@ describe('graph component', () => { it('renders the main columns in the graph', () => { expect(findStageColumns()).toHaveLength(defaultProps.pipeline.stages.length); }); + + describe('when column requests a refresh', () => { + beforeEach(() => { + findStageColumns() + .at(0) + .vm.$emit('refreshPipelineGraph'); + }); + + it('refreshPipelineGraph is emitted', () => { + expect(wrapper.emitted().refreshPipelineGraph).toHaveLength(1); + }); + }); }); describe('when linked pipelines are not present', () => { diff --git a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js index 7aab943446b..875aaa48037 100644 --- a/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js +++ b/spec/frontend/pipelines/graph/graph_component_wrapper_spec.js @@ -108,4 +108,17 @@ describe('Pipeline graph wrapper', () => { expect(getGraph().exists()).toBe(false); }); }); + + describe('when refresh action is emitted', () => { + beforeEach(async () => { + createComponentWithApollo(); + jest.spyOn(wrapper.vm.$apollo.queries.pipeline, 'refetch'); + await wrapper.vm.$nextTick(); + getGraph().vm.$emit('refreshPipelineGraph'); + }); + + it('calls refetch', () => { + expect(wrapper.vm.$apollo.queries.pipeline.refetch).toHaveBeenCalled(); + }); + }); }); diff --git a/spec/frontend/pipelines/graph/stage_column_component_spec.js b/spec/frontend/pipelines/graph/stage_column_component_spec.js index abf13823cb9..44803929f6d 100644 --- a/spec/frontend/pipelines/graph/stage_column_component_spec.js +++ b/spec/frontend/pipelines/graph/stage_column_component_spec.js @@ -1,5 +1,6 @@ import { mount, shallowMount } from '@vue/test-utils'; import ActionComponent from '~/pipelines/components/graph/action_component.vue'; +import JobItem from '~/pipelines/components/graph/job_item.vue'; import StageColumnComponent from '~/pipelines/components/graph/stage_column_component.vue'; const mockJob = { @@ -37,6 +38,7 @@ describe('stage column component', () => { const findStageColumnTitle = () => wrapper.find('[data-testid="stage-column-title"]'); const findStageColumnGroup = () => wrapper.find('[data-testid="stage-column-group"]'); const findAllStageColumnGroups = () => wrapper.findAll('[data-testid="stage-column-group"]'); + const findJobItem = () => wrapper.find(JobItem); const findActionComponent = () => wrapper.find(ActionComponent); const createComponent = ({ method = shallowMount, props = {} } = {}) => { @@ -67,6 +69,28 @@ describe('stage column component', () => { }); }); + describe('when job notifies action is complete', () => { + beforeEach(() => { + createComponent({ + method: mount, + props: { + groups: [ + { + title: 'Fish', + size: 1, + jobs: [mockJob], + }, + ], + }, + }); + findJobItem().vm.$emit('pipelineActionRequestComplete'); + }); + + it('emits refreshPipelineGraph', () => { + expect(wrapper.emitted().refreshPipelineGraph).toHaveLength(1); + }); + }); + describe('job', () => { beforeEach(() => { createComponent({ diff --git a/spec/frontend/vue_shared/components/callout_spec.js b/spec/frontend/vue_shared/components/callout_spec.js deleted file mode 100644 index 7c9bb6b4650..00000000000 --- a/spec/frontend/vue_shared/components/callout_spec.js +++ /dev/null @@ -1,63 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Callout from '~/vue_shared/components/callout.vue'; - -const TEST_MESSAGE = 'This is a callout message!'; -const TEST_SLOT = '<button>This is a callout slot!</button>'; - -describe('Callout Component', () => { - let wrapper; - - const factory = options => { - wrapper = shallowMount(Callout, { - ...options, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('should render the appropriate variant of callout', () => { - factory({ - propsData: { - category: 'info', - message: TEST_MESSAGE, - }, - }); - - expect(wrapper.classes()).toEqual(['bs-callout', 'bs-callout-info']); - - expect(wrapper.element.tagName).toEqual('DIV'); - }); - - it('should render accessibility attributes', () => { - factory({ - propsData: { - message: TEST_MESSAGE, - }, - }); - - expect(wrapper.attributes('role')).toEqual('alert'); - expect(wrapper.attributes('aria-live')).toEqual('assertive'); - }); - - it('should render the provided message', () => { - factory({ - propsData: { - message: TEST_MESSAGE, - }, - }); - - expect(wrapper.element.innerHTML.trim()).toEqual(TEST_MESSAGE); - }); - - it('should render the provided slot', () => { - factory({ - slots: { - default: TEST_SLOT, - }, - }); - - expect(wrapper.element.innerHTML.trim()).toEqual(TEST_SLOT); - }); -}); diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 2b6d865ee74..a71b0eb842a 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -147,6 +147,10 @@ RSpec.describe Project, factory_default: :keep do let(:container_without_wiki) { create(:project) } end + it_behaves_like 'can move repository storage' do + let_it_be(:container) { create(:project, :repository) } + end + it 'has an inverse relationship with merge requests' do expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project) end @@ -3040,50 +3044,6 @@ RSpec.describe Project, factory_default: :keep do end end - describe '#set_repository_read_only!' do - let(:project) { create(:project) } - - it 'makes the repository read-only' do - expect { project.set_repository_read_only! } - .to change(project, :repository_read_only?) - .from(false) - .to(true) - end - - it 'raises an error if the project is already read-only' do - project.set_repository_read_only! - - expect { project.set_repository_read_only! }.to raise_error(described_class::RepositoryReadOnlyError, /already read-only/) - end - - it 'raises an error when there is an existing git transfer in progress' do - allow(project).to receive(:git_transfer_in_progress?) { true } - - expect { project.set_repository_read_only! }.to raise_error(described_class::RepositoryReadOnlyError, /in progress/) - end - - context 'skip_git_transfer_check is true' do - it 'makes the project read-only when git transfers are in progress' do - allow(project).to receive(:git_transfer_in_progress?) { true } - - expect { project.set_repository_read_only!(skip_git_transfer_check: true) } - .to change(project, :repository_read_only?) - .from(false) - .to(true) - end - end - end - - describe '#set_repository_writable!' do - it 'sets repository_read_only to false' do - project = create(:project, :read_only) - - expect { project.set_repository_writable! } - .to change(project, :repository_read_only) - .from(true).to(false) - end - end - describe '#pushes_since_gc' do let(:project) { build_stubbed(:project) } diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 9cf22a9392d..a696c33837d 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -769,4 +769,30 @@ RSpec.describe Snippet do it { is_expected.to be_falsey } end end + + describe '#git_transfer_in_progress?' do + let(:snippet) { build(:snippet) } + + subject { snippet.git_transfer_in_progress? } + + it 'returns true when there are git transfers' do + allow(snippet).to receive(:reference_counter).with(type: Gitlab::GlRepository::SNIPPET) do + double(:reference_counter, value: 2) + end + + expect(subject).to eq true + end + + it 'returns false when there are not git transfers' do + allow(snippet).to receive(:reference_counter).with(type: Gitlab::GlRepository::SNIPPET) do + double(:reference_counter, value: 0) + end + + expect(subject).to eq false + end + end + + it_behaves_like 'can move repository storage' do + let_it_be(:container) { create(:snippet, :repository) } + end end diff --git a/spec/support/shared_examples/models/concerns/can_move_repository_storage_shared_examples.rb b/spec/support/shared_examples/models/concerns/can_move_repository_storage_shared_examples.rb new file mode 100644 index 00000000000..85a2c6f1449 --- /dev/null +++ b/spec/support/shared_examples/models/concerns/can_move_repository_storage_shared_examples.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +RSpec.shared_examples 'can move repository storage' do + let(:container) { raise NotImplementedError } + + describe '#set_repository_read_only!' do + it 'makes the repository read-only' do + expect { container.set_repository_read_only! } + .to change(container, :repository_read_only?) + .from(false) + .to(true) + end + + it 'raises an error if the project is already read-only' do + container.set_repository_read_only! + + expect { container.set_repository_read_only! }.to raise_error(described_class::RepositoryReadOnlyError, /already read-only/) + end + + it 'raises an error when there is an existing git transfer in progress' do + allow(container).to receive(:git_transfer_in_progress?) { true } + + expect { container.set_repository_read_only! }.to raise_error(described_class::RepositoryReadOnlyError, /in progress/) + end + + context 'skip_git_transfer_check is true' do + it 'makes the project read-only when git transfers are in progress' do + allow(container).to receive(:git_transfer_in_progress?) { true } + + expect { container.set_repository_read_only!(skip_git_transfer_check: true) } + .to change(container, :repository_read_only?) + .from(false) + .to(true) + end + end + end + + describe '#set_repository_writable!' do + it 'sets repository_read_only to false' do + expect { container.set_repository_writable! } + .to change(container, :repository_read_only) + .from(true).to(false) + end + end + + describe '#reference_counter' do + it 'returns a Gitlab::ReferenceCounter object' do + expect(Gitlab::ReferenceCounter).to receive(:new).with(container.repository.gl_repository).and_call_original + + result = container.reference_counter(type: container.repository.repo_type) + + expect(result).to be_a Gitlab::ReferenceCounter + end + end +end |