summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-12-23 06:10:22 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-12-23 06:10:22 +0000
commitde64b03b15fb40a3fc2f1897e8e4f6be50fd4403 (patch)
treeb49d88a83d9c113f7323fa2f47076a79429d9bc6
parent14c93e5de0adb01b79f618b0444237b3f5d0ea60 (diff)
downloadgitlab-ce-de64b03b15fb40a3fc2f1897e8e4f6be50fd4403.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue5
-rw-r--r--app/assets/javascripts/integrations/integration_settings_form.js5
-rw-r--r--app/assets/javascripts/pipeline_editor/components/text_editor.vue38
-rw-r--r--app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue74
-rw-r--r--app/controllers/concerns/show_inherited_labels_checker.rb11
-rw-r--r--app/controllers/groups/labels_controller.rb3
-rw-r--r--app/controllers/projects/labels_controller.rb3
-rw-r--r--app/models/project_pages_metadatum.rb3
-rw-r--r--app/services/pages/migrate_legacy_storage_to_deployment_service.rb8
-rw-r--r--app/services/pages/zip_directory_service.rb9
-rw-r--r--changelogs/unreleased/273544-add-group-mr-approval-settings-api.yml5
-rw-r--r--changelogs/unreleased/mjang-MR-approval-instance-level.yml5
-rw-r--r--changelogs/unreleased/pages-migration-task.yml5
-rw-r--r--config/feature_flags/development/show_inherited_labels.yml8
-rw-r--r--db/migrate/20201217070530_add_group_merge_request_approval_settings.rb24
-rw-r--r--db/schema_migrations/202012170705301
-rw-r--r--db/structure.sql13
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json100
-rw-r--r--lib/tasks/gitlab/pages.rake30
-rw-r--r--locale/gitlab.pot23
-rw-r--r--spec/controllers/groups/labels_controller_spec.rb41
-rw-r--r--spec/controllers/projects/labels_controller_spec.rb44
-rw-r--r--spec/frontend/pipeline_editor/components/text_editor_spec.js65
-rw-r--r--spec/frontend/pipeline_editor/mock_data.js4
-rw-r--r--spec/frontend/pipeline_editor/pipeline_editor_app_spec.js46
-rw-r--r--spec/models/project_pages_metadatum_spec.rb21
-rw-r--r--spec/services/pages/migrate_legacy_storage_to_deployment_service_spec.rb20
-rw-r--r--spec/tasks/gitlab/pages_rake_spec.rb67
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