diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-23 06:10:22 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-23 06:10:22 +0000 |
commit | de64b03b15fb40a3fc2f1897e8e4f6be50fd4403 (patch) | |
tree | b49d88a83d9c113f7323fa2f47076a79429d9bc6 | |
parent | 14c93e5de0adb01b79f618b0444237b3f5d0ea60 (diff) | |
download | gitlab-ce-de64b03b15fb40a3fc2f1897e8e4f6be50fd4403.tar.gz |
Add latest changes from gitlab-org/gitlab@master
28 files changed, 461 insertions, 220 deletions
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue index c6f8ba8dcb2..ac8a64d5f3b 100644 --- a/app/assets/javascripts/integrations/edit/components/integration_form.vue +++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue @@ -59,9 +59,6 @@ export default { showReset() { return this.isInstanceOrGroupLevel && this.propsSource.resetPath; }, - saveButtonKey() { - return `save-button-${this.isDisabled}`; - }, }, methods: { ...mapActions([ @@ -120,7 +117,6 @@ export default { <div v-if="isEditable" class="footer-block row-content-block"> <template v-if="isInstanceOrGroupLevel"> <gl-button - :key="saveButtonKey" v-gl-modal.confirmSaveIntegration category="primary" variant="success" @@ -134,7 +130,6 @@ export default { </template> <gl-button v-else - :key="saveButtonKey" category="primary" variant="success" type="submit" diff --git a/app/assets/javascripts/integrations/integration_settings_form.js b/app/assets/javascripts/integrations/integration_settings_form.js index 14d6f133d27..85fcbcec96a 100644 --- a/app/assets/javascripts/integrations/integration_settings_form.js +++ b/app/assets/javascripts/integrations/integration_settings_form.js @@ -1,4 +1,5 @@ import $ from 'jquery'; +import { delay } from 'lodash'; import axios from '../lib/utils/axios_utils'; import { __, s__ } from '~/locale'; import toast from '~/vue_shared/plugins/global_toast'; @@ -43,7 +44,9 @@ export default class IntegrationSettingsForm { const formValid = this.$form.get(0).checkValidity() || this.formActive === false; if (formValid) { - this.$form.submit(); + delay(() => { + this.$form.trigger('submit'); + }, 100); } else { eventHub.$emit('validateForm'); this.vue.$store.dispatch('setIsSaving', false); diff --git a/app/assets/javascripts/pipeline_editor/components/text_editor.vue b/app/assets/javascripts/pipeline_editor/components/text_editor.vue index 22f2a32c9ac..6954ba3d6ff 100644 --- a/app/assets/javascripts/pipeline_editor/components/text_editor.vue +++ b/app/assets/javascripts/pipeline_editor/components/text_editor.vue @@ -1,14 +1,50 @@ <script> import EditorLite from '~/vue_shared/components/editor_lite.vue'; +import { CiSchemaExtension } from '~/editor/editor_ci_schema_ext'; export default { components: { EditorLite, }, + inheritAttrs: false, + props: { + ciConfigPath: { + type: String, + required: true, + }, + commitSha: { + type: String, + required: false, + default: null, + }, + projectPath: { + type: String, + required: true, + }, + }, + methods: { + onEditorReady() { + const editorInstance = this.$refs.editor.getEditor(); + const [projectNamespace, projectPath] = this.projectPath.split('/'); + + editorInstance.use(new CiSchemaExtension()); + editorInstance.registerCiSchema({ + projectPath, + projectNamespace, + ref: this.commitSha, + }); + }, + }, }; </script> <template> <div class="gl-border-solid gl-border-gray-100 gl-border-1"> - <editor-lite file-name="*.yml" v-bind="$attrs" v-on="$listeners" /> + <editor-lite + ref="editor" + :file-name="ciConfigPath" + v-bind="$attrs" + @editor-ready="onEditorReady" + v-on="$listeners" + /> </div> </template> diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index 0d7191de116..a533721a057 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -1,7 +1,7 @@ <script> import { GlAlert, GlLoadingIcon, GlTab, GlTabs } from '@gitlab/ui'; import { __, s__, sprintf } from '~/locale'; -import { mergeUrlParams, redirectTo, refreshCurrentPage } from '~/lib/utils/url_utility'; +import { mergeUrlParams, redirectTo } from '~/lib/utils/url_utility'; import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin'; import PipelineGraph from '~/pipelines/components/pipeline_graph/pipeline_graph.vue'; @@ -18,6 +18,7 @@ const MR_SOURCE_BRANCH = 'merge_request[source_branch]'; const MR_TARGET_BRANCH = 'merge_request[target_branch]'; const COMMIT_FAILURE = 'COMMIT_FAILURE'; +const COMMIT_SUCCESS = 'COMMIT_SUCCESS'; const DEFAULT_FAILURE = 'DEFAULT_FAILURE'; const LOAD_FAILURE_NO_FILE = 'LOAD_FAILURE_NO_FILE'; const LOAD_FAILURE_NO_REF = 'LOAD_FAILURE_NO_REF'; @@ -67,10 +68,14 @@ export default { lastCommitSha: this.commitSha, currentTabIndex: 0, editorIsReady: false, - failureType: null, - failureReasons: [], isSaving: false, + + // Success and failure state + failureType: null, showFailureAlert: false, + failureReasons: [], + successType: null, + showSuccessAlert: false, }; }, apollo: { @@ -129,31 +134,42 @@ export default { defaultCommitMessage() { return sprintf(this.$options.i18n.defaultCommitMessage, { sourcePath: this.ciConfigPath }); }, + success() { + switch (this.successType) { + case COMMIT_SUCCESS: + return { + text: this.$options.alertTexts[COMMIT_SUCCESS], + variant: 'info', + }; + default: + return null; + } + }, failure() { switch (this.failureType) { case LOAD_FAILURE_NO_REF: return { - text: this.$options.errorTexts[LOAD_FAILURE_NO_REF], + text: this.$options.alertTexts[LOAD_FAILURE_NO_REF], variant: 'danger', }; case LOAD_FAILURE_NO_FILE: return { - text: this.$options.errorTexts[LOAD_FAILURE_NO_FILE], + text: this.$options.alertTexts[LOAD_FAILURE_NO_FILE], variant: 'danger', }; case LOAD_FAILURE_UNKNOWN: return { - text: this.$options.errorTexts[LOAD_FAILURE_UNKNOWN], + text: this.$options.alertTexts[LOAD_FAILURE_UNKNOWN], variant: 'danger', }; case COMMIT_FAILURE: return { - text: this.$options.errorTexts[COMMIT_FAILURE], + text: this.$options.alertTexts[COMMIT_FAILURE], variant: 'danger', }; default: return { - text: this.$options.errorTexts[DEFAULT_FAILURE], + text: this.$options.alertTexts[DEFAULT_FAILURE], variant: 'danger', }; } @@ -165,13 +181,15 @@ export default { tabGraph: s__('Pipelines|Visualize'), tabLint: s__('Pipelines|Lint'), }, - errorTexts: { + alertTexts: { + [COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'), + [COMMIT_SUCCESS]: __('Your changes have been successfully committed.'), + [DEFAULT_FAILURE]: __('Something went wrong on our end.'), + [LOAD_FAILURE_NO_FILE]: s__('Pipelines|No CI file found in this repository, please add one.'), [LOAD_FAILURE_NO_REF]: s__( 'Pipelines|Repository does not have a default branch, please set one.', ), - [LOAD_FAILURE_NO_FILE]: s__('Pipelines|No CI file found in this repository, please add one.'), [LOAD_FAILURE_UNKNOWN]: s__('Pipelines|The CI configuration was not loaded, please try again.'), - [COMMIT_FAILURE]: s__('Pipelines|The GitLab CI configuration could not be updated.'), }, methods: { handleBlobContentError(error = {}) { @@ -188,6 +206,7 @@ export default { this.reportFailure(LOAD_FAILURE_UNKNOWN); } }, + dismissFailure() { this.showFailureAlert = false; }, @@ -196,6 +215,14 @@ export default { this.failureType = type; this.failureReasons = reasons; }, + dismissSuccess() { + this.showSuccessAlert = false; + }, + reportSuccess(type) { + this.showSuccessAlert = true; + this.successType = type; + }, + redirectToNewMergeRequest(sourceBranch) { const url = mergeUrlParams( { @@ -236,13 +263,10 @@ export default { if (openMergeRequest) { this.redirectToNewMergeRequest(branch); } else { - this.lastCommitSha = commit.sha; + this.reportSuccess(COMMIT_SUCCESS); - // Note: The page should not be refreshed, and we - // would display an alert to notify users the - // commit was succesful. See: - // https://gitlab.com/gitlab-org/gitlab/-/issues/292229 - refreshCurrentPage(); + // Update latest commit + this.lastCommitSha = commit.sha; } } catch (error) { this.reportFailure(COMMIT_FAILURE, [error?.message]); @@ -260,6 +284,14 @@ export default { <template> <div class="gl-mt-4"> <gl-alert + v-if="showSuccessAlert" + :variant="success.variant" + :dismissible="true" + @dismiss="dismissSuccess" + > + {{ success.text }} + </gl-alert> + <gl-alert v-if="showFailureAlert" :variant="failure.variant" :dismissible="true" @@ -277,7 +309,13 @@ export default { <!-- editor should be mounted when its tab is visible, so the container has a size --> <gl-tab :title="$options.i18n.tabEdit" :lazy="!editorIsReady"> <!-- editor should be mounted only once, when the tab is displayed --> - <text-editor v-model="contentModel" @editor-ready="editorIsReady = true" /> + <text-editor + v-model="contentModel" + :ci-config-path="ciConfigPath" + :commit-sha="lastCommitSha" + :project-path="projectPath" + @editor-ready="editorIsReady = true" + /> </gl-tab> <gl-tab diff --git a/app/controllers/concerns/show_inherited_labels_checker.rb b/app/controllers/concerns/show_inherited_labels_checker.rb deleted file mode 100644 index 9847226f599..00000000000 --- a/app/controllers/concerns/show_inherited_labels_checker.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module ShowInheritedLabelsChecker - extend ActiveSupport::Concern - - private - - def show_inherited_labels?(include_ancestor_groups) - Feature.enabled?(:show_inherited_labels, @project || @group, default_enabled: true) || include_ancestor_groups # rubocop:disable Gitlab/ModuleWithInstanceVariables - end -end diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 34856f8d84e..c5dd3e1df35 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -2,7 +2,6 @@ class Groups::LabelsController < Groups::ApplicationController include ToggleSubscriptionAction - include ShowInheritedLabelsChecker before_action :label, only: [:edit, :update, :destroy] before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] @@ -112,7 +111,7 @@ class Groups::LabelsController < Groups::ApplicationController current_user, group_id: @group.id, only_group_labels: options[:only_group_labels], - include_ancestor_groups: show_inherited_labels?(params[:include_ancestor_groups]), + include_ancestor_groups: true, sort: sort, subscribed: options[:subscribed], include_descendant_groups: options[:include_descendant_groups], diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index ba8e6b90971..3992165d07c 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -2,7 +2,6 @@ class Projects::LabelsController < Projects::ApplicationController include ToggleSubscriptionAction - include ShowInheritedLabelsChecker before_action :check_issuables_available! before_action :label, only: [:edit, :update, :destroy, :promote] @@ -164,7 +163,7 @@ class Projects::LabelsController < Projects::ApplicationController @available_labels ||= LabelsFinder.new(current_user, project_id: @project.id, - include_ancestor_groups: show_inherited_labels?(params[:include_ancestor_groups]), + include_ancestor_groups: true, search: params[:search], subscribed: params[:subscribed], sort: sort).execute diff --git a/app/models/project_pages_metadatum.rb b/app/models/project_pages_metadatum.rb index bd1919fe7ed..2bef0056732 100644 --- a/app/models/project_pages_metadatum.rb +++ b/app/models/project_pages_metadatum.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ProjectPagesMetadatum < ApplicationRecord + include EachBatch + self.primary_key = :project_id belongs_to :project, inverse_of: :pages_metadatum @@ -8,4 +10,5 @@ class ProjectPagesMetadatum < ApplicationRecord belongs_to :pages_deployment scope :deployed, -> { where(deployed: true) } + scope :only_on_legacy_storage, -> { deployed.where(pages_deployment: nil) } end diff --git a/app/services/pages/migrate_legacy_storage_to_deployment_service.rb b/app/services/pages/migrate_legacy_storage_to_deployment_service.rb index 7a580e1a097..62245097c06 100644 --- a/app/services/pages/migrate_legacy_storage_to_deployment_service.rb +++ b/app/services/pages/migrate_legacy_storage_to_deployment_service.rb @@ -2,7 +2,8 @@ module Pages class MigrateLegacyStorageToDeploymentService - ExclusiveLeaseTaken = Class.new(StandardError) + ExclusiveLeaseTakenError = Class.new(StandardError) + FailedToCreateArchiveError = Class.new(StandardError) include ::Pages::LegacyStorageLease @@ -19,7 +20,7 @@ module Pages true end - raise ExclusiveLeaseTaken, "Can't migrate pages for project #{project.id}: exclusive lease taken" unless migrated + raise ExclusiveLeaseTakenError, "Can't migrate pages for project #{project.id}: exclusive lease taken" unless migrated end private @@ -38,13 +39,12 @@ module Pages project.set_first_pages_deployment!(deployment) rescue ::Pages::ZipDirectoryService::InvalidArchiveError => e - Gitlab::ErrorTracking.track_exception(e, project_id: project.id) - if !project.pages_metadatum&.reload&.pages_deployment && Feature.enabled?(:pages_migration_mark_as_not_deployed, project) project.mark_pages_as_not_deployed end + raise FailedToCreateArchiveError, e ensure FileUtils.rm_f(archive_path) if archive_path end diff --git a/app/services/pages/zip_directory_service.rb b/app/services/pages/zip_directory_service.rb index 5166526dc4c..71a5955cdcc 100644 --- a/app/services/pages/zip_directory_service.rb +++ b/app/services/pages/zip_directory_service.rb @@ -4,8 +4,9 @@ module Pages class ZipDirectoryService include Gitlab::Utils::StrongMemoize - InvalidArchiveError = Class.new(RuntimeError) - InvalidEntryError = Class.new(RuntimeError) + Error = Class.new(::StandardError) + InvalidArchiveError = Class.new(Error) + InvalidEntryError = Class.new(Error) PUBLIC_DIR = 'public' @@ -14,7 +15,7 @@ module Pages end def execute - raise InvalidArchiveError unless valid_work_directory? + raise InvalidArchiveError, "Invalid work directory: #{@input_dir}" unless valid_work_directory? output_file = File.join(real_dir, "@migrated.zip") # '@' to avoid any name collision with groups or projects @@ -39,7 +40,7 @@ module Pages unless valid_path?(disk_file_path) # archive without public directory is completelly unusable - raise InvalidArchiveError if zipfile_path == PUBLIC_DIR + raise InvalidArchiveError, "Invalid public directory: #{disk_file_path}" if zipfile_path == PUBLIC_DIR # archive with invalid entry will just have this entry missing raise InvalidEntryError diff --git a/changelogs/unreleased/273544-add-group-mr-approval-settings-api.yml b/changelogs/unreleased/273544-add-group-mr-approval-settings-api.yml new file mode 100644 index 00000000000..6ea5f8e8ad0 --- /dev/null +++ b/changelogs/unreleased/273544-add-group-mr-approval-settings-api.yml @@ -0,0 +1,5 @@ +--- +title: Add group MR approval settings table +merge_request: 50256 +author: +type: added diff --git a/changelogs/unreleased/mjang-MR-approval-instance-level.yml b/changelogs/unreleased/mjang-MR-approval-instance-level.yml new file mode 100644 index 00000000000..b0b9c73a221 --- /dev/null +++ b/changelogs/unreleased/mjang-MR-approval-instance-level.yml @@ -0,0 +1,5 @@ +--- +title: Updated UI text to match style guidelines +merge_request: 49871 +author: +type: other diff --git a/changelogs/unreleased/pages-migration-task.yml b/changelogs/unreleased/pages-migration-task.yml new file mode 100644 index 00000000000..7038c16bbcf --- /dev/null +++ b/changelogs/unreleased/pages-migration-task.yml @@ -0,0 +1,5 @@ +--- +title: Add rake task for migrating legacy pages storage to zip deployments +merge_request: 50153 +author: +type: added diff --git a/config/feature_flags/development/show_inherited_labels.yml b/config/feature_flags/development/show_inherited_labels.yml deleted file mode 100644 index 1ee1daaf0af..00000000000 --- a/config/feature_flags/development/show_inherited_labels.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: show_inherited_labels -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/42960 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/267547 -milestone: '13.5' -type: development -group: group::project management -default_enabled: true diff --git a/db/migrate/20201217070530_add_group_merge_request_approval_settings.rb b/db/migrate/20201217070530_add_group_merge_request_approval_settings.rb new file mode 100644 index 00000000000..dba9e6e440f --- /dev/null +++ b/db/migrate/20201217070530_add_group_merge_request_approval_settings.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class AddGroupMergeRequestApprovalSettings < ActiveRecord::Migration[6.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + with_lock_retries do + create_table :group_merge_request_approval_settings, id: false do |t| + t.timestamps_with_timezone null: false + t.references :group, references: :namespaces, primary_key: true, default: nil, index: false, + foreign_key: { to_table: :namespaces, on_delete: :cascade } + t.boolean :allow_author_approval, null: false, default: false + end + end + end + + def down + with_lock_retries do + drop_table :group_merge_request_approval_settings + end + end +end diff --git a/db/schema_migrations/20201217070530 b/db/schema_migrations/20201217070530 new file mode 100644 index 00000000000..c09f3b03e3b --- /dev/null +++ b/db/schema_migrations/20201217070530 @@ -0,0 +1 @@ +59e40a24e8422e64bc85c4d54e6da923512017bac6a167350affeff241046e9f
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index b66cda410c0..f16ecc2f472 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -12949,6 +12949,13 @@ CREATE SEQUENCE group_import_states_group_id_seq ALTER SEQUENCE group_import_states_group_id_seq OWNED BY group_import_states.group_id; +CREATE TABLE group_merge_request_approval_settings ( + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + group_id bigint NOT NULL, + allow_author_approval boolean DEFAULT false NOT NULL +); + CREATE TABLE group_wiki_repositories ( shard_id bigint NOT NULL, group_id bigint NOT NULL, @@ -19680,6 +19687,9 @@ ALTER TABLE ONLY group_group_links ALTER TABLE ONLY group_import_states ADD CONSTRAINT group_import_states_pkey PRIMARY KEY (group_id); +ALTER TABLE ONLY group_merge_request_approval_settings + ADD CONSTRAINT group_merge_request_approval_settings_pkey PRIMARY KEY (group_id); + ALTER TABLE ONLY group_wiki_repositories ADD CONSTRAINT group_wiki_repositories_pkey PRIMARY KEY (group_id); @@ -24345,6 +24355,9 @@ ALTER TABLE ONLY merge_request_blocks ALTER TABLE ONLY merge_request_reviewers ADD CONSTRAINT fk_rails_3704a66140 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE; +ALTER TABLE ONLY group_merge_request_approval_settings + ADD CONSTRAINT fk_rails_37b6b4cdba FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; + ALTER TABLE ONLY analytics_cycle_analytics_project_stages ADD CONSTRAINT fk_rails_3829e49b66 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json index ee3f33e7f09..cf63865ad7d 100644 --- a/doc/api/graphql/reference/gitlab_schema.json +++ b/doc/api/graphql/reference/gitlab_schema.json @@ -10286,9 +10286,13 @@ ], "type": { - "kind": "SCALAR", - "name": "Time", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Time", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null @@ -10346,9 +10350,13 @@ ], "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null @@ -10360,9 +10368,13 @@ ], "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null @@ -10374,9 +10386,13 @@ ], "type": { - "kind": "SCALAR", - "name": "String", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null @@ -10420,9 +10436,13 @@ ], "type": { - "kind": "SCALAR", - "name": "Int", - "ofType": null + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } }, "isDeprecated": false, "deprecationReason": null @@ -10933,13 +10953,9 @@ ], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Time", - "ofType": null - } + "kind": "SCALAR", + "name": "Time", + "ofType": null }, "isDeprecated": false, "deprecationReason": null @@ -10951,13 +10967,9 @@ ], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null }, "isDeprecated": false, "deprecationReason": null @@ -11023,13 +11035,9 @@ ], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null }, "isDeprecated": false, "deprecationReason": null @@ -11041,13 +11049,9 @@ ], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null }, "isDeprecated": false, "deprecationReason": null @@ -11059,13 +11063,9 @@ ], "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "Int", - "ofType": null - } + "kind": "SCALAR", + "name": "Int", + "ofType": null }, "isDeprecated": false, "deprecationReason": null diff --git a/lib/tasks/gitlab/pages.rake b/lib/tasks/gitlab/pages.rake new file mode 100644 index 00000000000..4b728c7fc6d --- /dev/null +++ b/lib/tasks/gitlab/pages.rake @@ -0,0 +1,30 @@ +require 'logger' + +namespace :gitlab do + namespace :pages do + desc "GitLab | Pages | Migrate legacy storage to zip format" + task migrate_legacy_storage: :gitlab_environment do + logger = Logger.new(STDOUT) + logger.info('Starting to migrate legacy pages storage to zip deployments') + migrated_projects = 0 + + ProjectPagesMetadatum.only_on_legacy_storage.each_batch(of: 10) do |batch| + batch.preload(project: [:namespace, :route, pages_metadatum: :pages_deployment]).each do |metadatum| + project = metadatum.project + time = Benchmark.realtime do + ::Pages::MigrateLegacyStorageToDeploymentService.new(project).execute + end + + migrated_projects += 1 + + logger.info("project_id: #{project.id} #{project.pages_path} has been migrated in #{time} seconds") + rescue => e + logger.error("#{e.message} project_id: #{project&.id}") + Gitlab::ErrorTracking.track_exception(e, project_id: project&.id) + end + + logger.info("#{migrated_projects} pages projects are migrated") + end + end + end +end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 6bbd806e07a..f6b4e5ceb3e 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -17341,6 +17341,9 @@ msgstr "" msgid "Merge request %{mr_link} was reviewed by %{mr_author}" msgstr "" +msgid "Merge request (MR) approvals" +msgstr "" + msgid "Merge request approvals" msgstr "" @@ -17356,9 +17359,6 @@ msgstr "" msgid "Merge requests" msgstr "" -msgid "Merge requests approvals" -msgstr "" - msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others" msgstr "" @@ -21019,6 +21019,9 @@ msgstr "" msgid "Prev" msgstr "" +msgid "Prevent MR approvals by author." +msgstr "" + msgid "Prevent MR approvals by the author." msgstr "" @@ -21028,12 +21031,6 @@ msgstr "" msgid "Prevent adding new members to project membership within this group" msgstr "" -msgid "Prevent approval of merge requests by merge request author" -msgstr "" - -msgid "Prevent approval of merge requests by merge request committers" -msgstr "" - msgid "Prevent environment from auto-stopping" msgstr "" @@ -21043,7 +21040,7 @@ msgstr "" msgid "Prevent users from changing their profile name" msgstr "" -msgid "Prevent users from modifying merge request approvers list" +msgid "Prevent users from modifying MR approval rules." msgstr "" msgid "Prevent users from performing write operations on GitLab while performing maintenance." @@ -23025,6 +23022,9 @@ msgstr "" msgid "Registry setup" msgstr "" +msgid "Regulate approvals by authors/committers. Affects all projects." +msgstr "" + msgid "Reindexing status" msgstr "" @@ -25465,9 +25465,6 @@ msgstr "" msgid "Settings related to the use and experience of using GitLab's Package Registry." msgstr "" -msgid "Settings to prevent self-approval across all projects in the instance. Only an administrator can modify these settings." -msgstr "" - msgid "Setup" msgstr "" diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index 33041f1af9f..b2320615778 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -9,8 +9,6 @@ RSpec.describe Groups::LabelsController do before do group.add_owner(user) - # by default FFs are enabled in specs so we turn it off - stub_feature_flags(show_inherited_labels: false) sign_in(user) end @@ -34,41 +32,12 @@ RSpec.describe Groups::LabelsController do subgroup.add_owner(user) end - RSpec.shared_examples 'returns ancestor group labels' do - it 'returns ancestor group labels' do - get :index, params: params, format: :json + it 'returns ancestor group labels' do + params = { group_id: subgroup, only_group_labels: true } + get :index, params: params, format: :json - label_ids = json_response.map {|label| label['title']} - expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) - end - end - - context 'when include_ancestor_groups true' do - let(:params) { { group_id: subgroup, include_ancestor_groups: true, only_group_labels: true } } - - it_behaves_like 'returns ancestor group labels' - end - - context 'when include_ancestor_groups false' do - let(:params) { { group_id: subgroup, only_group_labels: true } } - - it 'does not return ancestor group labels', :aggregate_failures do - get :index, params: params, format: :json - - label_ids = json_response.map {|label| label['title']} - expect(label_ids).to match_array([subgroup_label_1.title]) - expect(label_ids).not_to include([group_label_1.title]) - end - end - - context 'when show_inherited_labels enabled' do - let(:params) { { group_id: subgroup } } - - before do - stub_feature_flags(show_inherited_labels: true) - end - - it_behaves_like 'returns ancestor group labels' + label_ids = json_response.map {|label| label['title']} + expect(label_ids).to match_array([group_label_1.title, subgroup_label_1.title]) end end diff --git a/spec/controllers/projects/labels_controller_spec.rb b/spec/controllers/projects/labels_controller_spec.rb index 8a3c55033cb..f452c22a5ca 100644 --- a/spec/controllers/projects/labels_controller_spec.rb +++ b/spec/controllers/projects/labels_controller_spec.rb @@ -84,46 +84,12 @@ RSpec.describe Projects::LabelsController do create(:label_priority, project: project, label: subgroup_label_2, priority: 1) end - RSpec.shared_examples 'returns ancestor group labels' do - it 'returns ancestor group labels', :aggregate_failures do - get :index, params: params + it 'returns ancestor group labels', :aggregate_failures do + params = { namespace_id: project.namespace.to_param, project_id: project } + get :index, params: params - expect(assigns(:labels)).to match_array([subgroup_label_1] + group_labels + project_labels) - expect(assigns(:prioritized_labels)).to match_array([subgroup_label_2] + group_priority_labels + project_priority_labels) - end - end - - context 'when show_inherited_labels disabled' do - before do - stub_feature_flags(show_inherited_labels: false) - end - - context 'when include_ancestor_groups false' do - let(:params) { { namespace_id: project.namespace.to_param, project_id: project } } - - it 'does not return ancestor group labels', :aggregate_failures do - get :index, params: params - - expect(assigns(:labels)).to match_array([subgroup_label_1] + project_labels) - expect(assigns(:prioritized_labels)).to match_array([subgroup_label_2] + project_priority_labels) - end - end - - context 'when include_ancestor_groups true' do - let(:params) { { namespace_id: project.namespace.to_param, project_id: project, include_ancestor_groups: true } } - - it_behaves_like 'returns ancestor group labels' - end - end - - context 'when show_inherited_labels enabled' do - let(:params) { { namespace_id: project.namespace.to_param, project_id: project } } - - before do - stub_feature_flags(show_inherited_labels: true) - end - - it_behaves_like 'returns ancestor group labels' + expect(assigns(:labels)).to match_array([subgroup_label_1] + group_labels + project_labels) + expect(assigns(:prioritized_labels)).to match_array([subgroup_label_2] + group_priority_labels + project_priority_labels) end end diff --git a/spec/frontend/pipeline_editor/components/text_editor_spec.js b/spec/frontend/pipeline_editor/components/text_editor_spec.js index 18f71ebc95c..3d3bb4f78c5 100644 --- a/spec/frontend/pipeline_editor/components/text_editor_spec.js +++ b/spec/frontend/pipeline_editor/components/text_editor_spec.js @@ -1,30 +1,67 @@ import { shallowMount } from '@vue/test-utils'; -import EditorLite from '~/vue_shared/components/editor_lite.vue'; -import { mockCiYml } from '../mock_data'; +import { + mockCiConfigPath, + mockCiYml, + mockCommitSha, + mockProjectPath, + mockNamespace, + mockProjectName, +} from '../mock_data'; import TextEditor from '~/pipeline_editor/components/text_editor.vue'; describe('~/pipeline_editor/components/text_editor.vue', () => { let wrapper; - const editorReadyListener = jest.fn(); - const createComponent = (attrs = {}, mountFn = shallowMount) => { + let editorReadyListener; + let mockUse; + let mockRegisterCiSchema; + + const MockEditorLite = { + template: '<div/>', + props: ['value', 'fileName'], + mounted() { + this.$emit('editor-ready'); + }, + methods: { + getEditor: () => ({ + use: mockUse, + registerCiSchema: mockRegisterCiSchema, + }), + }, + }; + + const createComponent = (opts = {}, mountFn = shallowMount) => { wrapper = mountFn(TextEditor, { + propsData: { + ciConfigPath: mockCiConfigPath, + commitSha: mockCommitSha, + projectPath: mockProjectPath, + }, attrs: { value: mockCiYml, - ...attrs, }, listeners: { 'editor-ready': editorReadyListener, }, + stubs: { + EditorLite: MockEditorLite, + }, + ...opts, }); }; - const findEditor = () => wrapper.find(EditorLite); + const findEditor = () => wrapper.find(MockEditorLite); + + beforeEach(() => { + editorReadyListener = jest.fn(); + mockUse = jest.fn(); + mockRegisterCiSchema = jest.fn(); - it('contains an editor', () => { createComponent(); + }); + it('contains an editor', () => { expect(findEditor().exists()).toBe(true); }); @@ -32,8 +69,18 @@ describe('~/pipeline_editor/components/text_editor.vue', () => { expect(findEditor().props('value')).toBe(mockCiYml); }); - it('editor is configured for .yml', () => { - expect(findEditor().props('fileName')).toBe('*.yml'); + it('editor is configured for the CI config path', () => { + expect(findEditor().props('fileName')).toBe(mockCiConfigPath); + }); + + it('editor is configured with syntax highligting', async () => { + expect(mockUse).toHaveBeenCalledTimes(1); + expect(mockRegisterCiSchema).toHaveBeenCalledTimes(1); + expect(mockRegisterCiSchema).toHaveBeenCalledWith({ + projectNamespace: mockNamespace, + projectPath: mockProjectName, + ref: mockCommitSha, + }); }); it('bubbles up events', () => { diff --git a/spec/frontend/pipeline_editor/mock_data.js b/spec/frontend/pipeline_editor/mock_data.js index fbf75247334..e02c94e6558 100644 --- a/spec/frontend/pipeline_editor/mock_data.js +++ b/spec/frontend/pipeline_editor/mock_data.js @@ -1,6 +1,8 @@ import { CI_CONFIG_STATUS_VALID } from '~/pipeline_editor/constants'; -export const mockProjectPath = 'user1/project1'; +export const mockNamespace = 'user1'; +export const mockProjectName = 'project1'; +export const mockProjectPath = `${mockNamespace}/${mockProjectName}`; export const mockDefaultBranch = 'master'; export const mockNewMergeRequestPath = '/-/merge_requests/new'; export const mockCommitSha = 'aabbccdd'; diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js index ce37164e2a4..1e09c311439 100644 --- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js +++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js @@ -42,6 +42,10 @@ jest.mock('~/lib/utils/url_utility', () => ({ mergeUrlParams: jest.requireActual('~/lib/utils/url_utility').mergeUrlParams, })); +const MockEditorLite = { + template: '<div/>', +}; + describe('~/pipeline_editor/pipeline_editor_app.vue', () => { let wrapper; @@ -87,9 +91,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { GlTabs, GlButton, CommitForm, - EditorLite: { - template: '<div/>', - }, + EditorLite: MockEditorLite, TextEditor, }, mocks: { @@ -140,6 +142,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { const findTabAt = i => wrapper.findAll(GlTab).at(i); const findVisualizationTab = () => wrapper.find('[data-testid="visualization-tab"]'); const findTextEditor = () => wrapper.find(TextEditor); + const findEditorLite = () => wrapper.find(MockEditorLite); const findCommitForm = () => wrapper.find(CommitForm); const findPipelineGraph = () => wrapper.find(PipelineGraph); const findCommitBtnLoadingIcon = () => wrapper.find('[type="submit"]').find(GlLoadingIcon); @@ -236,7 +239,14 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { it('displays content after the query loads', () => { expect(findLoadingIcon().exists()).toBe(false); - expect(findTextEditor().attributes('value')).toBe(mockCiYml); + + expect(findEditorLite().attributes('value')).toBe(mockCiYml); + expect(findEditorLite().attributes('file-name')).toBe(mockCiConfigPath); + }); + + it('configures text editor', () => { + expect(findTextEditor().props('commitSha')).toBe(mockCommitSha); + expect(findTextEditor().props('projectPath')).toBe(mockProjectPath); }); describe('commit form', () => { @@ -284,13 +294,29 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { }); }); - it('refreshes the page', () => { - expect(refreshCurrentPage).toHaveBeenCalled(); + it('displays an alert to indicate success', () => { + expect(findAlert().text()).toMatchInterpolatedText( + 'Your changes have been successfully committed.', + ); }); it('shows no saving state', () => { expect(findCommitBtnLoadingIcon().exists()).toBe(false); }); + + it('a second commit submits the latest sha, keeping the form updated', async () => { + await submitCommit(); + + expect(mockMutate).toHaveBeenCalledTimes(2); + expect(mockMutate).toHaveBeenLastCalledWith({ + mutation: expect.any(Object), + variables: { + ...mockVariables, + lastCommitId: mockCommitNextSha, + branch: mockDefaultBranch, + }, + }); + }); }); describe('when the user commits changes to a new branch', () => { @@ -311,10 +337,6 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { }, }); }); - - it('refreshes the page', () => { - expect(refreshCurrentPage).toHaveBeenCalledWith(); - }); }); describe('when the user commits changes to open a new merge request', () => { @@ -389,7 +411,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { it('content is restored after cancel is called', async () => { await cancelCommitForm(); - expect(findTextEditor().attributes('value')).toBe(mockCiYml); + expect(findEditorLite().attributes('value')).toBe(mockCiYml); }); }); }); @@ -403,7 +425,7 @@ describe('~/pipeline_editor/pipeline_editor_app.vue', () => { await waitForPromises(); expect(findAlert().exists()).toBe(false); - expect(findTextEditor().attributes('value')).toBe(mockCiYml); + expect(findEditorLite().attributes('value')).toBe(mockCiYml); }); it('shows a 404 error message', async () => { diff --git a/spec/models/project_pages_metadatum_spec.rb b/spec/models/project_pages_metadatum_spec.rb new file mode 100644 index 00000000000..31a533e0363 --- /dev/null +++ b/spec/models/project_pages_metadatum_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ProjectPagesMetadatum do + describe '.only_on_legacy_storage' do + it 'returns only deployed records without deployment' do + create(:project) # without pages deployed + + legacy_storage_project = create(:project) + legacy_storage_project.mark_pages_as_deployed + + project_with_deployment = create(:project) + deployment = create(:pages_deployment, project: project_with_deployment) + project_with_deployment.mark_pages_as_deployed + project_with_deployment.update_pages_deployment!(deployment) + + expect(described_class.only_on_legacy_storage).to eq([legacy_storage_project.pages_metadatum]) + end + end +end diff --git a/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb b/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb index 0c6afec30db..7c6e0afdb0b 100644 --- a/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb +++ b/spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb @@ -9,9 +9,13 @@ RSpec.describe Pages::MigrateLegacyStorageToDeploymentService do it 'marks pages as not deployed if public directory is absent' do project.mark_pages_as_deployed + expect(project.pages_metadatum.reload.deployed).to eq(true) + expect do service.execute - end.to change { project.pages_metadatum.reload.deployed }.from(true).to(false) + end.to raise_error(described_class::FailedToCreateArchiveError) + + expect(project.pages_metadatum.reload.deployed).to eq(false) end it 'does not mark pages as not deployed if public directory is absent but pages_deployment exists' do @@ -19,9 +23,13 @@ RSpec.describe Pages::MigrateLegacyStorageToDeploymentService do project.update_pages_deployment!(deployment) project.mark_pages_as_deployed + expect(project.pages_metadatum.reload.deployed).to eq(true) + expect do service.execute - end.not_to change { project.pages_metadatum.reload.deployed }.from(true) + end.to raise_error(described_class::FailedToCreateArchiveError) + + expect(project.pages_metadatum.reload.deployed).to eq(true) end it 'does not mark pages as not deployed if public directory is absent but feature is disabled' do @@ -29,9 +37,13 @@ RSpec.describe Pages::MigrateLegacyStorageToDeploymentService do project.mark_pages_as_deployed + expect(project.pages_metadatum.reload.deployed).to eq(true) + expect do service.execute - end.not_to change { project.pages_metadatum.reload.deployed }.from(true) + end.to raise_error(described_class::FailedToCreateArchiveError) + + expect(project.pages_metadatum.reload.deployed).to eq(true) end it 'removes pages archive when can not save deployment' do @@ -94,7 +106,7 @@ RSpec.describe Pages::MigrateLegacyStorageToDeploymentService do described_class.new(project).try_obtain_lease do expect do described_class.new(project).execute - end.to raise_error(described_class::ExclusiveLeaseTaken) + end.to raise_error(described_class::ExclusiveLeaseTakenError) end end end diff --git a/spec/tasks/gitlab/pages_rake_spec.rb b/spec/tasks/gitlab/pages_rake_spec.rb new file mode 100644 index 00000000000..76808f52890 --- /dev/null +++ b/spec/tasks/gitlab/pages_rake_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true + +require 'rake_helper' + +RSpec.describe 'gitlab:pages:migrate_legacy_storagerake task' do + before(:context) do + Rake.application.rake_require 'tasks/gitlab/pages' + end + + subject { run_rake_task('gitlab:pages:migrate_legacy_storage') } + + let(:project) { create(:project) } + + it 'does not try to migrate pages if pages are not deployed' do + expect(::Pages::MigrateLegacyStorageToDeploymentService).not_to receive(:new) + + subject + end + + context 'when pages are marked as deployed' do + before do + project.mark_pages_as_deployed + end + + context 'when pages directory does not exist' do + it 'tries to migrate the project, but does not crash' do + expect_next_instance_of(::Pages::MigrateLegacyStorageToDeploymentService, project) do |service| + expect(service).to receive(:execute).and_call_original + end + + subject + end + end + + context 'when pages directory exists on disk' do + before do + FileUtils.mkdir_p File.join(project.pages_path, "public") + File.open(File.join(project.pages_path, "public/index.html"), "w") do |f| + f.write("Hello!") + end + end + + it 'migrates pages projects without deployments' do + expect_next_instance_of(::Pages::MigrateLegacyStorageToDeploymentService, project) do |service| + expect(service).to receive(:execute).and_call_original + end + + expect do + subject + end.to change { project.pages_metadatum.reload.pages_deployment }.from(nil) + end + + context 'when deployed already exists for the project' do + before do + deployment = create(:pages_deployment, project: project) + project.set_first_pages_deployment!(deployment) + end + + it 'does not try to migrate project' do + expect(::Pages::MigrateLegacyStorageToDeploymentService).not_to receive(:new) + + subject + end + end + end + end +end |