diff --git a/Gemfile.checksum b/Gemfile.checksum
index 588ac56255b..759d4d8ee61 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -220,7 +220,7 @@
{"name":"gitlab-glfm-markdown","version":"0.0.17","platform":"x86_64-darwin","checksum":"50e0f4865ef7c455426c7c058fc10ff9c8366482d48a63d6f6693b38c4a49c1c"},
{"name":"gitlab-glfm-markdown","version":"0.0.17","platform":"x86_64-linux","checksum":"cc877ff8ceb3aa8a331fdb8991592e35897823e0f77ba9e4b2b65082c665089b"},
{"name":"gitlab-labkit","version":"0.36.0","platform":"ruby","checksum":"35f21d1c3870ed0c9b8321e25d0b0b0b5021805a5d0525d1eb0fde6b103af981"},
-{"name":"gitlab-license","version":"2.4.0","platform":"ruby","checksum":"fd238fb1e605a6b9250d4eb1744434ffd131f18d50a3be32f613c883f7635e20"},
+{"name":"gitlab-license","version":"2.5.0","platform":"ruby","checksum":"4c166c469c2ad17876ca43188a4ccebe3feb0726c4c1770047f8dcef96573f4d"},
{"name":"gitlab-mail_room","version":"0.0.25","platform":"ruby","checksum":"223ce7c3c0797b6015eaa37147884e6ddc7be9a7ee90a424358c96bc18613b1a"},
{"name":"gitlab-markup","version":"1.9.0","platform":"ruby","checksum":"7eda045a08ec2d110084252fa13a8c9eac8bdac0e302035ca7db4b82bcbd7ed4"},
{"name":"gitlab-net-dns","version":"0.9.2","platform":"ruby","checksum":"f726d978479d43810819f12a45c0906d775a07e34df111bbe693fffbbef3059d"},
diff --git a/Gemfile.lock b/Gemfile.lock
index a298d719530..ba040213e67 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -712,7 +712,7 @@ GEM
opentracing (~> 0.4)
pg_query (>= 4.2.3, < 6.0)
redis (> 3.0.0, < 6.0.0)
- gitlab-license (2.4.0)
+ gitlab-license (2.5.0)
gitlab-mail_room (0.0.25)
jwt (>= 2.0)
net-imap (>= 0.2.1)
diff --git a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
index f60f242e56d..46fc6153b38 100644
--- a/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
+++ b/app/assets/javascripts/import_entities/import_groups/components/import_table.vue
@@ -637,6 +637,7 @@ export default {
gitlabLogo: window.gon.gitlab_logo,
PAGE_SIZES,
permissionsHelpPath: helpPagePath('user/permissions', { anchor: 'group-members-permissions' }),
+ betaFeatureHelpPath: helpPagePath('policy/experiment-beta-support', { anchor: 'beta-features' }),
popoverOptions: { title: __('What is listed here?') },
i18n,
LOCAL_STORAGE_KEY: 'gl-bulk-imports-status-page-size-v1',
@@ -802,6 +803,23 @@ export default {
data-testid="import-projects-warning"
/>
+
+
+
+
+ {{
+ content
+ }}
+
+
{
this.resetFile();
this.alert = { message: this.$options.i18n.successfulUpload, variant: 'success' };
diff --git a/app/assets/javascripts/ml/model_registry/components/model_create.vue b/app/assets/javascripts/ml/model_registry/components/model_create.vue
index 33825891a18..a7514f14cfb 100644
--- a/app/assets/javascripts/ml/model_registry/components/model_create.vue
+++ b/app/assets/javascripts/ml/model_registry/components/model_create.vue
@@ -28,7 +28,7 @@ export default {
GlFormTextarea,
ImportArtifactZone: () => import('./import_artifact_zone.vue'),
},
- inject: ['projectPath'],
+ inject: ['projectPath', 'maxAllowedFileSize'],
props: {
createModelVisible: {
type: Boolean,
@@ -106,6 +106,7 @@ export default {
importPath,
file: this.selectedFile.file,
subfolder: this.selectedFile.subfolder,
+ maxAllowedFileSize: this.maxAllowedFileSize,
});
const { showPath } = this.versionData.mlModelVersionCreate.modelVersion._links;
@@ -118,7 +119,6 @@ export default {
} catch (error) {
Sentry.captureException(error);
this.errorMessage = error;
- this.selectedFile = emptyArtifactFile;
this.showModal();
}
},
@@ -258,9 +258,13 @@ export default {
- {{
- errorMessage
- }}
+ {{ errorMessage }}
diff --git a/app/assets/javascripts/ml/model_registry/components/model_version_create.vue b/app/assets/javascripts/ml/model_registry/components/model_version_create.vue
index 778bbf0f358..764580539c0 100644
--- a/app/assets/javascripts/ml/model_registry/components/model_version_create.vue
+++ b/app/assets/javascripts/ml/model_registry/components/model_version_create.vue
@@ -27,7 +27,7 @@ export default {
GlFormTextarea,
ImportArtifactZone: () => import('./import_artifact_zone.vue'),
},
- inject: ['projectPath'],
+ inject: ['projectPath', 'maxAllowedFileSize'],
props: {
modelGid: {
type: String,
@@ -78,6 +78,7 @@ export default {
importPath,
file: this.selectedFile.file,
subfolder: this.selectedFile.subfolder,
+ maxAllowedFileSize: this.maxAllowedFileSize,
});
const { showPath } = this.versionData.mlModelVersionCreate.modelVersion._links;
visitUrl(showPath);
@@ -85,7 +86,6 @@ export default {
} catch (error) {
Sentry.captureException(error);
this.errorMessage = error;
- this.selectedFile = emptyArtifactFile;
this.showModal();
}
},
diff --git a/app/assets/javascripts/ml/model_registry/services/upload_model.js b/app/assets/javascripts/ml/model_registry/services/upload_model.js
index dfd37dba294..3ddc02d3297 100644
--- a/app/assets/javascripts/ml/model_registry/services/upload_model.js
+++ b/app/assets/javascripts/ml/model_registry/services/upload_model.js
@@ -1,11 +1,30 @@
import axios from '~/lib/utils/axios_utils';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
import { contentTypeMultipartFormData } from '~/lib/utils/headers';
import { joinPaths } from '~/lib/utils/url_utility';
+import { s__, sprintf } from '~/locale';
-export const uploadModel = ({ importPath, file, subfolder }) => {
+export const uploadModel = ({ importPath, file, subfolder, maxAllowedFileSize }) => {
if (!file) {
return Promise.resolve();
}
+ if (!maxAllowedFileSize) {
+ return Promise.resolve(s__('Mlmodelregistry|Provide the max allowed file size'));
+ }
+
+ if (file.size > maxAllowedFileSize) {
+ const errorMessage = sprintf(
+ s__(
+ 'MlModelRegistry|File "%{name}" is %{size}. It is larger than max allowed size of %{maxAllowedFileSize}',
+ ),
+ {
+ name: file.name,
+ size: numberToHumanSize(file.size),
+ maxAllowedFileSize: numberToHumanSize(maxAllowedFileSize),
+ },
+ );
+ return Promise.reject(new Error(errorMessage));
+ }
const formData = new FormData();
const importUrl = joinPaths(importPath, subfolder, encodeURIComponent(file.name));
diff --git a/app/assets/javascripts/super_sidebar/components/user_bar.vue b/app/assets/javascripts/super_sidebar/components/user_bar.vue
index af405812711..9ea9412a3f5 100644
--- a/app/assets/javascripts/super_sidebar/components/user_bar.vue
+++ b/app/assets/javascripts/super_sidebar/components/user_bar.vue
@@ -44,7 +44,7 @@ export default {
import(/* webpackChunkName: 'organization_switcher' */ './organization_switcher.vue'),
},
i18n: {
- issues: __('Issues'),
+ issues: __('Assigned issues'),
mergeRequests: __('Merge requests'),
searchKbdHelp: sprintf(
s__('GlobalSearch|Type %{kbdOpen}/%{kbdClose} to search'),
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index 45cbe909a27..4398dcbe742 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -36,7 +36,7 @@ module PreferencesHelper
followed_user_activity: _("Followed Users' Activity"),
groups: _("Your Groups"),
todos: _("Your To-Do List"),
- issues: _("Assigned Issues"),
+ issues: _("Assigned issues"),
merge_requests: _("Assigned merge requests"),
operations: _("Operations Dashboard")
}.with_indifferent_access.freeze
diff --git a/app/helpers/projects/ml/model_registry_helper.rb b/app/helpers/projects/ml/model_registry_helper.rb
index f98f3411036..ea45d177003 100644
--- a/app/helpers/projects/ml/model_registry_helper.rb
+++ b/app/helpers/projects/ml/model_registry_helper.rb
@@ -10,7 +10,8 @@ module Projects
projectPath: project.full_path,
create_model_path: new_project_ml_model_path(project),
can_write_model_registry: can_write_model_registry?(user, project),
- mlflow_tracking_url: mlflow_tracking_url(project)
+ mlflow_tracking_url: mlflow_tracking_url(project),
+ max_allowed_file_size: max_allowed_file_size(project)
}
to_json(data)
@@ -25,7 +26,8 @@ module Projects
can_write_model_registry: can_write_model_registry?(user, project),
mlflow_tracking_url: mlflow_tracking_url(project),
model_id: model.id,
- model_name: model.name
+ model_name: model.name,
+ max_allowed_file_size: max_allowed_file_size(project)
}
to_json(data)
@@ -42,7 +44,8 @@ module Projects
version_name: model_version.version,
can_write_model_registry: can_write_model_registry?(user, project),
import_path: model_version_artifact_import_path(project.id, model_version.id),
- model_path: project_ml_model_path(project, model_version.model)
+ model_path: project_ml_model_path(project, model_version.model),
+ max_allowed_file_size: max_allowed_file_size(project)
}
to_json(data)
@@ -62,6 +65,10 @@ module Projects
user&.can?(:write_model_registry, project)
end
+ def max_allowed_file_size(project)
+ project.actual_limits.ml_model_max_file_size
+ end
+
def to_json(data)
Gitlab::Json.generate(data.deep_transform_keys { |k| k.to_s.camelize(:lower) })
end
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index cae3d58abac..8bf7e7ad970 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -1,4 +1,4 @@
-- page_title _("Issues")
+- page_title _("Assigned issues")
- @breadcrumb_link = issues_dashboard_path(assignee_username: current_user.username)
- add_page_specific_style 'page_bundles/issuable_list'
- add_page_specific_style 'page_bundles/dashboard'
@@ -9,7 +9,7 @@
= render_if_exists 'shared/dashboard/saml_reauth_notice',
groups_requiring_saml_reauth: user_groups_requiring_reauth
-= render ::Layouts::PageHeadingComponent.new(_('Issues')) do |c|
+= render ::Layouts::PageHeadingComponent.new(_('Assigned issues')) do |c|
- c.with_actions do
= render 'shared/new_project_item_vue_select'
diff --git a/db/docs/batched_background_migrations/backfill_project_relation_exports_project_id.yml b/db/docs/batched_background_migrations/backfill_project_relation_exports_project_id.yml
new file mode 100644
index 00000000000..b65ea819b1d
--- /dev/null
+++ b/db/docs/batched_background_migrations/backfill_project_relation_exports_project_id.yml
@@ -0,0 +1,9 @@
+---
+migration_job_name: BackfillProjectRelationExportsProjectId
+description: Backfills sharding key `project_relation_exports.project_id` from `project_export_jobs`.
+feature_category: importers
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/155429
+milestone: '17.1'
+queued_migration_version: 20240605113250
+finalize_after: '2024-07-22'
+finalized_by: # version of the migration that finalized this BBM
diff --git a/db/docs/batched_background_migrations/backfill_terraform_state_versions_project_id.yml b/db/docs/batched_background_migrations/backfill_terraform_state_versions_project_id.yml
new file mode 100644
index 00000000000..a3f27a08aa7
--- /dev/null
+++ b/db/docs/batched_background_migrations/backfill_terraform_state_versions_project_id.yml
@@ -0,0 +1,9 @@
+---
+migration_job_name: BackfillTerraformStateVersionsProjectId
+description: Backfills sharding key `terraform_state_versions.project_id` from `terraform_states`.
+feature_category: infrastructure_as_code
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/155438
+milestone: '17.1'
+queued_migration_version: 20240605132810
+finalize_after: '2024-07-22'
+finalized_by: # version of the migration that finalized this BBM
diff --git a/db/docs/project_relation_exports.yml b/db/docs/project_relation_exports.yml
index 9f7c8fb03d4..0e994abdd6c 100644
--- a/db/docs/project_relation_exports.yml
+++ b/db/docs/project_relation_exports.yml
@@ -19,3 +19,4 @@ desired_sharding_key:
table: project_export_jobs
sharding_key: project_id
belongs_to: project_export_job
+desired_sharding_key_migration_job_name: BackfillProjectRelationExportsProjectId
diff --git a/db/docs/terraform_state_versions.yml b/db/docs/terraform_state_versions.yml
index dbcf459ccfc..e9863e2c1cb 100644
--- a/db/docs/terraform_state_versions.yml
+++ b/db/docs/terraform_state_versions.yml
@@ -4,7 +4,8 @@ classes:
- Terraform::StateVersion
feature_categories:
- infrastructure_as_code
-description: Represents a Terraform state file at a point in time, with a corresponding file stored in object storage
+description: Represents a Terraform state file at a point in time, with a corresponding
+ file stored in object storage
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35211
milestone: '13.4'
gitlab_schema: gitlab_main_cell
@@ -19,3 +20,4 @@ desired_sharding_key:
table: terraform_states
sharding_key: project_id
belongs_to: terraform_state
+desired_sharding_key_migration_job_name: BackfillTerraformStateVersionsProjectId
diff --git a/db/migrate/20240605113246_add_project_id_to_project_relation_exports.rb b/db/migrate/20240605113246_add_project_id_to_project_relation_exports.rb
new file mode 100644
index 00000000000..a5e3a41ac05
--- /dev/null
+++ b/db/migrate/20240605113246_add_project_id_to_project_relation_exports.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddProjectIdToProjectRelationExports < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+
+ def change
+ add_column :project_relation_exports, :project_id, :bigint
+ end
+end
diff --git a/db/migrate/20240605132806_add_project_id_to_terraform_state_versions.rb b/db/migrate/20240605132806_add_project_id_to_terraform_state_versions.rb
new file mode 100644
index 00000000000..6fa7cb03206
--- /dev/null
+++ b/db/migrate/20240605132806_add_project_id_to_terraform_state_versions.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddProjectIdToTerraformStateVersions < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+
+ def change
+ add_column :terraform_state_versions, :project_id, :bigint
+ end
+end
diff --git a/db/post_migrate/20240605113247_index_project_relation_exports_on_project_id.rb b/db/post_migrate/20240605113247_index_project_relation_exports_on_project_id.rb
new file mode 100644
index 00000000000..57d9884470e
--- /dev/null
+++ b/db/post_migrate/20240605113247_index_project_relation_exports_on_project_id.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class IndexProjectRelationExportsOnProjectId < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_project_relation_exports_on_project_id'
+
+ def up
+ add_concurrent_index :project_relation_exports, :project_id, name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :project_relation_exports, INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20240605113248_add_project_relation_exports_project_id_fk.rb b/db/post_migrate/20240605113248_add_project_relation_exports_project_id_fk.rb
new file mode 100644
index 00000000000..4556fb7b44f
--- /dev/null
+++ b/db/post_migrate/20240605113248_add_project_relation_exports_project_id_fk.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddProjectRelationExportsProjectIdFk < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :project_relation_exports, :projects, column: :project_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :project_relation_exports, column: :project_id
+ end
+ end
+end
diff --git a/db/post_migrate/20240605113249_add_project_relation_exports_project_id_trigger.rb b/db/post_migrate/20240605113249_add_project_relation_exports_project_id_trigger.rb
new file mode 100644
index 00000000000..f4484447cf5
--- /dev/null
+++ b/db/post_migrate/20240605113249_add_project_relation_exports_project_id_trigger.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddProjectRelationExportsProjectIdTrigger < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+
+ def up
+ install_sharding_key_assignment_trigger(
+ table: :project_relation_exports,
+ sharding_key: :project_id,
+ parent_table: :project_export_jobs,
+ parent_sharding_key: :project_id,
+ foreign_key: :project_export_job_id
+ )
+ end
+
+ def down
+ remove_sharding_key_assignment_trigger(
+ table: :project_relation_exports,
+ sharding_key: :project_id,
+ parent_table: :project_export_jobs,
+ parent_sharding_key: :project_id,
+ foreign_key: :project_export_job_id
+ )
+ end
+end
diff --git a/db/post_migrate/20240605113250_queue_backfill_project_relation_exports_project_id.rb b/db/post_migrate/20240605113250_queue_backfill_project_relation_exports_project_id.rb
new file mode 100644
index 00000000000..5b55e292dbf
--- /dev/null
+++ b/db/post_migrate/20240605113250_queue_backfill_project_relation_exports_project_id.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class QueueBackfillProjectRelationExportsProjectId < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+ restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
+
+ MIGRATION = "BackfillProjectRelationExportsProjectId"
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 1000
+ SUB_BATCH_SIZE = 100
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :project_relation_exports,
+ :id,
+ :project_id,
+ :project_export_jobs,
+ :project_id,
+ :project_export_job_id,
+ job_interval: DELAY_INTERVAL,
+ batch_size: BATCH_SIZE,
+ sub_batch_size: SUB_BATCH_SIZE
+ )
+ end
+
+ def down
+ delete_batched_background_migration(
+ MIGRATION,
+ :project_relation_exports,
+ :id,
+ [
+ :project_id,
+ :project_export_jobs,
+ :project_id,
+ :project_export_job_id
+ ]
+ )
+ end
+end
diff --git a/db/post_migrate/20240605132807_index_terraform_state_versions_on_project_id.rb b/db/post_migrate/20240605132807_index_terraform_state_versions_on_project_id.rb
new file mode 100644
index 00000000000..4f65707ecc7
--- /dev/null
+++ b/db/post_migrate/20240605132807_index_terraform_state_versions_on_project_id.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class IndexTerraformStateVersionsOnProjectId < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_terraform_state_versions_on_project_id'
+
+ def up
+ add_concurrent_index :terraform_state_versions, :project_id, name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index_by_name :terraform_state_versions, INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20240605132808_add_terraform_state_versions_project_id_fk.rb b/db/post_migrate/20240605132808_add_terraform_state_versions_project_id_fk.rb
new file mode 100644
index 00000000000..c705c155de4
--- /dev/null
+++ b/db/post_migrate/20240605132808_add_terraform_state_versions_project_id_fk.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddTerraformStateVersionsProjectIdFk < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :terraform_state_versions, :projects, column: :project_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :terraform_state_versions, column: :project_id
+ end
+ end
+end
diff --git a/db/post_migrate/20240605132809_add_terraform_state_versions_project_id_trigger.rb b/db/post_migrate/20240605132809_add_terraform_state_versions_project_id_trigger.rb
new file mode 100644
index 00000000000..071994aff5a
--- /dev/null
+++ b/db/post_migrate/20240605132809_add_terraform_state_versions_project_id_trigger.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+class AddTerraformStateVersionsProjectIdTrigger < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+
+ def up
+ install_sharding_key_assignment_trigger(
+ table: :terraform_state_versions,
+ sharding_key: :project_id,
+ parent_table: :terraform_states,
+ parent_sharding_key: :project_id,
+ foreign_key: :terraform_state_id
+ )
+ end
+
+ def down
+ remove_sharding_key_assignment_trigger(
+ table: :terraform_state_versions,
+ sharding_key: :project_id,
+ parent_table: :terraform_states,
+ parent_sharding_key: :project_id,
+ foreign_key: :terraform_state_id
+ )
+ end
+end
diff --git a/db/post_migrate/20240605132810_queue_backfill_terraform_state_versions_project_id.rb b/db/post_migrate/20240605132810_queue_backfill_terraform_state_versions_project_id.rb
new file mode 100644
index 00000000000..5acf58c9b0b
--- /dev/null
+++ b/db/post_migrate/20240605132810_queue_backfill_terraform_state_versions_project_id.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+class QueueBackfillTerraformStateVersionsProjectId < Gitlab::Database::Migration[2.2]
+ milestone '17.1'
+ restrict_gitlab_migration gitlab_schema: :gitlab_main_cell
+
+ MIGRATION = "BackfillTerraformStateVersionsProjectId"
+ DELAY_INTERVAL = 2.minutes
+ BATCH_SIZE = 1000
+ SUB_BATCH_SIZE = 100
+
+ def up
+ queue_batched_background_migration(
+ MIGRATION,
+ :terraform_state_versions,
+ :id,
+ :project_id,
+ :terraform_states,
+ :project_id,
+ :terraform_state_id,
+ job_interval: DELAY_INTERVAL,
+ batch_size: BATCH_SIZE,
+ sub_batch_size: SUB_BATCH_SIZE
+ )
+ end
+
+ def down
+ delete_batched_background_migration(
+ MIGRATION,
+ :terraform_state_versions,
+ :id,
+ [
+ :project_id,
+ :terraform_states,
+ :project_id,
+ :terraform_state_id
+ ]
+ )
+ end
+end
diff --git a/db/schema_migrations/20240605113246 b/db/schema_migrations/20240605113246
new file mode 100644
index 00000000000..8322e1f1b67
--- /dev/null
+++ b/db/schema_migrations/20240605113246
@@ -0,0 +1 @@
+0633b0f78ffa5b7550bb6c08df4d1992d137e703fad7be7dfbeb8f1947043b28
\ No newline at end of file
diff --git a/db/schema_migrations/20240605113247 b/db/schema_migrations/20240605113247
new file mode 100644
index 00000000000..febf70fb544
--- /dev/null
+++ b/db/schema_migrations/20240605113247
@@ -0,0 +1 @@
+e437a04b396cc520e4efdfba802a6fa019ebe42ef56556f6456c96c957017dfc
\ No newline at end of file
diff --git a/db/schema_migrations/20240605113248 b/db/schema_migrations/20240605113248
new file mode 100644
index 00000000000..b38dbd43802
--- /dev/null
+++ b/db/schema_migrations/20240605113248
@@ -0,0 +1 @@
+01772232031f3546e17b11e863114eb97908f657d9525136c5810cc1254ae14c
\ No newline at end of file
diff --git a/db/schema_migrations/20240605113249 b/db/schema_migrations/20240605113249
new file mode 100644
index 00000000000..c8bf95c0c29
--- /dev/null
+++ b/db/schema_migrations/20240605113249
@@ -0,0 +1 @@
+cbbdfc5caaa00e2fc0b458a6a8e555ed033f066d85f7a363726fdb66ff4aad8f
\ No newline at end of file
diff --git a/db/schema_migrations/20240605113250 b/db/schema_migrations/20240605113250
new file mode 100644
index 00000000000..1998b175aca
--- /dev/null
+++ b/db/schema_migrations/20240605113250
@@ -0,0 +1 @@
+333432f11538c10e77ac8f73bf0b364c3230999ff718d7e25b7e0df33c372e56
\ No newline at end of file
diff --git a/db/schema_migrations/20240605132806 b/db/schema_migrations/20240605132806
new file mode 100644
index 00000000000..8d44133edee
--- /dev/null
+++ b/db/schema_migrations/20240605132806
@@ -0,0 +1 @@
+48616de4dab655eb5a01da5f7b8149c3d5ca66c64cdccea86468a3b6a9e83237
\ No newline at end of file
diff --git a/db/schema_migrations/20240605132807 b/db/schema_migrations/20240605132807
new file mode 100644
index 00000000000..12d876ada6a
--- /dev/null
+++ b/db/schema_migrations/20240605132807
@@ -0,0 +1 @@
+63ab2d580707d4a7095ccdfcbd636b701708b5b415284112084446b21ee35807
\ No newline at end of file
diff --git a/db/schema_migrations/20240605132808 b/db/schema_migrations/20240605132808
new file mode 100644
index 00000000000..54914dc23ee
--- /dev/null
+++ b/db/schema_migrations/20240605132808
@@ -0,0 +1 @@
+8e168eb75e5ab533b51be54c9df72249fc166f1d0fc3f1f396c87058c4116501
\ No newline at end of file
diff --git a/db/schema_migrations/20240605132809 b/db/schema_migrations/20240605132809
new file mode 100644
index 00000000000..99346006c8a
--- /dev/null
+++ b/db/schema_migrations/20240605132809
@@ -0,0 +1 @@
+0e2504f0165fbeb417a7a5ff3650f0cb9aa151ff9f72e5c773659e4c3f4075c5
\ No newline at end of file
diff --git a/db/schema_migrations/20240605132810 b/db/schema_migrations/20240605132810
new file mode 100644
index 00000000000..bb5bac4f808
--- /dev/null
+++ b/db/schema_migrations/20240605132810
@@ -0,0 +1 @@
+c4eaba0dc0a28d090b3d79f49752ad929d0ed89ca4cfb422c6f15087ba81e31a
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index effddaa342d..5c9376fcb3b 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -1153,6 +1153,22 @@ RETURN NEW;
END
$$;
+CREATE FUNCTION trigger_b2612138515d() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+IF NEW."project_id" IS NULL THEN
+ SELECT "project_id"
+ INTO NEW."project_id"
+ FROM "project_export_jobs"
+ WHERE "project_export_jobs"."id" = NEW."project_export_job_id";
+END IF;
+
+RETURN NEW;
+
+END
+$$;
+
CREATE FUNCTION trigger_b4520c29ea74() RETURNS trigger
LANGUAGE plpgsql
AS $$
@@ -1233,6 +1249,22 @@ RETURN NEW;
END
$$;
+CREATE FUNCTION trigger_d4487a75bd44() RETURNS trigger
+ LANGUAGE plpgsql
+ AS $$
+BEGIN
+IF NEW."project_id" IS NULL THEN
+ SELECT "project_id"
+ INTO NEW."project_id"
+ FROM "terraform_states"
+ WHERE "terraform_states"."id" = NEW."terraform_state_id";
+END IF;
+
+RETURN NEW;
+
+END
+$$;
+
CREATE FUNCTION trigger_dbdd61a66a91() RETURNS trigger
LANGUAGE plpgsql
AS $$
@@ -15146,6 +15178,7 @@ CREATE TABLE project_relation_exports (
relation text NOT NULL,
jid text,
export_error text,
+ project_id bigint,
CONSTRAINT check_15e644d856 CHECK ((char_length(jid) <= 255)),
CONSTRAINT check_4b5880b795 CHECK ((char_length(relation) <= 255)),
CONSTRAINT check_dbd1cf73d0 CHECK ((char_length(export_error) <= 300))
@@ -17367,6 +17400,7 @@ CREATE TABLE terraform_state_versions (
ci_build_id bigint,
verification_started_at timestamp with time zone,
verification_state smallint DEFAULT 0 NOT NULL,
+ project_id bigint,
CONSTRAINT check_0824bb7bbd CHECK ((char_length(file) <= 255)),
CONSTRAINT tf_state_versions_verification_failure_text_limit CHECK ((char_length(verification_failure) <= 255))
);
@@ -27609,6 +27643,8 @@ CREATE INDEX index_project_pages_metadata_on_project_id_and_deployed_is_true ON
CREATE INDEX index_project_relation_export_upload_id ON project_relation_export_uploads USING btree (project_relation_export_id);
+CREATE INDEX index_project_relation_exports_on_project_id ON project_relation_exports USING btree (project_id);
+
CREATE UNIQUE INDEX index_project_repositories_on_disk_path ON project_repositories USING btree (disk_path);
CREATE UNIQUE INDEX index_project_repositories_on_project_id ON project_repositories USING btree (project_id);
@@ -28241,6 +28277,8 @@ CREATE INDEX index_terraform_state_versions_on_ci_build_id ON terraform_state_ve
CREATE INDEX index_terraform_state_versions_on_created_by_user_id ON terraform_state_versions USING btree (created_by_user_id);
+CREATE INDEX index_terraform_state_versions_on_project_id ON terraform_state_versions USING btree (project_id);
+
CREATE UNIQUE INDEX index_terraform_state_versions_on_state_id_and_version ON terraform_state_versions USING btree (terraform_state_id, version);
CREATE INDEX index_terraform_state_versions_on_verification_state ON terraform_state_versions USING btree (verification_state);
@@ -30663,6 +30701,8 @@ CREATE TRIGGER trigger_a1bc7c70cbdf BEFORE INSERT OR UPDATE ON vulnerability_use
CREATE TRIGGER trigger_a253cb3cacdf BEFORE INSERT OR UPDATE ON dora_daily_metrics FOR EACH ROW EXECUTE FUNCTION trigger_a253cb3cacdf();
+CREATE TRIGGER trigger_b2612138515d BEFORE INSERT OR UPDATE ON project_relation_exports FOR EACH ROW EXECUTE FUNCTION trigger_b2612138515d();
+
CREATE TRIGGER trigger_b4520c29ea74 BEFORE INSERT OR UPDATE ON approval_merge_request_rule_sources FOR EACH ROW EXECUTE FUNCTION trigger_b4520c29ea74();
CREATE TRIGGER trigger_c17a166692a2 BEFORE INSERT OR UPDATE ON audit_events_streaming_headers FOR EACH ROW EXECUTE FUNCTION trigger_c17a166692a2();
@@ -30675,6 +30715,8 @@ CREATE TRIGGER trigger_c9090feed334 BEFORE INSERT OR UPDATE ON boards_epic_lists
CREATE TRIGGER trigger_catalog_resource_sync_event_on_project_update AFTER UPDATE ON projects FOR EACH ROW WHEN ((((old.name)::text IS DISTINCT FROM (new.name)::text) OR (old.description IS DISTINCT FROM new.description) OR (old.visibility_level IS DISTINCT FROM new.visibility_level))) EXECUTE FUNCTION insert_catalog_resource_sync_event();
+CREATE TRIGGER trigger_d4487a75bd44 BEFORE INSERT OR UPDATE ON terraform_state_versions FOR EACH ROW EXECUTE FUNCTION trigger_d4487a75bd44();
+
CREATE TRIGGER trigger_dbdd61a66a91 BEFORE INSERT OR UPDATE ON agent_activity_events FOR EACH ROW EXECUTE FUNCTION trigger_dbdd61a66a91();
CREATE TRIGGER trigger_delete_project_namespace_on_project_delete AFTER DELETE ON projects FOR EACH ROW WHEN ((old.project_namespace_id IS NOT NULL)) EXECUTE FUNCTION delete_associated_project_namespace();
@@ -30859,6 +30901,9 @@ ALTER TABLE ONLY scan_result_policy_violations
ALTER TABLE ONLY incident_management_timeline_events
ADD CONSTRAINT fk_1800597ef9 FOREIGN KEY (author_id) REFERENCES users(id) ON DELETE SET NULL;
+ALTER TABLE ONLY terraform_state_versions
+ ADD CONSTRAINT fk_180cde327a FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY project_features
ADD CONSTRAINT fk_18513d9b92 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
@@ -31312,6 +31357,9 @@ ALTER TABLE ONLY users
ALTER TABLE ONLY analytics_devops_adoption_snapshots
ADD CONSTRAINT fk_78c9eac821 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY project_relation_exports
+ ADD CONSTRAINT fk_7a4d3d5c0f FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY lists
ADD CONSTRAINT fk_7a5553d60f FOREIGN KEY (label_id) REFERENCES labels(id) ON DELETE CASCADE;
diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md
index c2896f1be26..6f7e01fbcd2 100644
--- a/doc/administration/auth/oidc.md
+++ b/doc/administration/auth/oidc.md
@@ -295,7 +295,7 @@ gitlab_rails['omniauth_providers'] = [
name: "azure_oauth2",
label: "Azure OIDC", # optional label for login button, defaults to "Openid Connect"
args: {
- name: "azure_activedirectory_v2",
+ name: "azure_oauth2",
strategy_class: "OmniAuth::Strategies::OpenIDConnect",
scope: ["openid", "profile", "email"],
response_type: "code",
@@ -319,7 +319,7 @@ gitlab_rails['omniauth_providers'] = [
```ruby
gitlab_rails['omniauth_providers'] = [
{
- name: "azure_oauth2",
+ name: "azure_activedirectory_v2",
label: "Azure OIDC", # optional label for login button, defaults to "Openid Connect"
args: {
name: "azure_activedirectory_v2",
diff --git a/doc/administration/settings/import_and_export_settings.md b/doc/administration/settings/import_and_export_settings.md
index 247fc47c9ae..be47f1047fa 100644
--- a/doc/administration/settings/import_and_export_settings.md
+++ b/doc/administration/settings/import_and_export_settings.md
@@ -39,12 +39,18 @@ To enable the export of
## Enable migration of groups and projects by direct transfer
+DETAILS:
+**Status:** Beta
+
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/383268) in GitLab 15.8.
-> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/461326) in GitLab 17.1.
WARNING:
In GitLab 16.1 and earlier, you should **not** use direct transfer with [scheduled scan execution policies](../../user/application_security/policies/scan-execution-policies.md). If using direct transfer, first upgrade to GitLab 16.2 and ensure security policy bots are enabled in the projects you are enforcing.
+WARNING:
+This feature is in [beta](../../policy/experiment-beta-support.md#beta) and subject to change without notice.
+This feature is not ready for production use.
+
Migration of groups and projects by direct transfer is disabled by default.
To enable migration of groups and projects by direct transfer:
diff --git a/doc/api/bulk_imports.md b/doc/api/bulk_imports.md
index 3e23449f44e..69f086a9253 100644
--- a/doc/api/bulk_imports.md
+++ b/doc/api/bulk_imports.md
@@ -15,6 +15,10 @@ DETAILS:
With the group migration by direct transfer API, you can start and view the progress of migrations initiated with
[group migration by direct transfer](../user/group/import/index.md).
+WARNING:
+Migrating projects with this API is in [beta](../policy/experiment-beta-support.md#beta). This feature is not
+ready for production use.
+
## Prerequisites
For information on prerequisites for group migration by direct transfer API, see
@@ -27,7 +31,7 @@ prerequisites for [migrating groups by direct transfer](../user/group/import/dir
Use this endpoint to start a new group or project migration. Specify:
- `entities[group_entity]` to migrate a group.
-- `entities[project_entity]` to migrate a project.
+- `entities[project_entity]` to migrate a project. (**Status:** Beta)
```plaintext
POST /bulk_imports
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index f3490b641e3..2ea9ed6e235 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -906,7 +906,7 @@ Returns [`[CiRunnerUsage!]`](#cirunnerusage).
| Name | Type | Description |
| ---- | ---- | ----------- |
| `fromDate` | [`Date`](#date) | Start of the requested date frame. Defaults to the start of the previous calendar month. |
-| `fullPath` | [`ID`](#id) | Filter jobs by the full path of the group or project they belong to. For example, `gitlab-org` or `gitlab-org/gitlab`. Available only to admins, group maintainers (when a group is specified), or project maintainers (when a project is specified). Limited to runners from 5000 child projects. |
+| `fullPath` | [`ID`](#id) | Filter jobs by the full path of the group or project they belong to. For example, `gitlab-org` or `gitlab-org/gitlab`. Available only to administrators and users with the Maintainer role for the group (when a group is specified), or project (when a project is specified). Limited to runners from 5000 child projects. |
| `runnerType` | [`CiRunnerType`](#cirunnertype) | Filter runners by the type. |
| `runnersLimit` | [`Int`](#int) | Maximum number of runners to return. Other runners will be aggregated to a `runner: null` entry. Defaults to 5 if unspecified. Maximum of 500. |
| `toDate` | [`Date`](#date) | End of the requested date frame. Defaults to the end of the previous calendar month. |
@@ -926,7 +926,7 @@ Returns [`[CiRunnerUsageByProject!]`](#cirunnerusagebyproject).
| Name | Type | Description |
| ---- | ---- | ----------- |
| `fromDate` | [`Date`](#date) | Start of the requested date frame. Defaults to the start of the previous calendar month. |
-| `fullPath` | [`ID`](#id) | Filter jobs based on the full path of the group or project they belong to. For example, `gitlab-org` or `gitlab-org/gitlab`. Available only to admins, group maintainers (when a group is specified), or project maintainers (when a project is specified). Limited to runners from 5000 child projects. |
+| `fullPath` | [`ID`](#id) | Filter jobs based on the full path of the group or project they belong to. For example, `gitlab-org` or `gitlab-org/gitlab`. Available only to administrators and users with the Maintainer role for the group (when a group is specified), or project (when a project is specified). Limited to runners from 5000 child projects. |
| `projectsLimit` | [`Int`](#int) | Maximum number of projects to return. Other projects will be aggregated to a `project: null` entry. Defaults to 5 if unspecified. Maximum of 500. |
| `runnerType` | [`CiRunnerType`](#cirunnertype) | Filter jobs by the type of runner that executed them. |
| `toDate` | [`Date`](#date) | End of the requested date frame. Defaults to the end of the previous calendar month. |
@@ -8064,6 +8064,7 @@ Input type: `RunnersExportUsageInput`
| ---- | ---- | ----------- |
| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| `fromDate` | [`ISO8601Date`](#iso8601date) | UTC start date of the period to report on. Defaults to the start of last full month. |
+| `fullPath` | [`ID`](#id) | Filter jobs by the full path of the group or project they belong to. For example, `gitlab-org` or `gitlab-org/gitlab`. Available only to administrators and users with the Maintainer role for the group (when a group is specified), or project (when a project is specified). Limited to runners from 5000 child projects. |
| `maxProjectCount` | [`Int`](#int) | Maximum number of projects to return. All other runner usage will be attributed to an `` entry. Defaults to 1000 projects. |
| `runnerType` | [`CiRunnerType`](#cirunnertype) | Scope of the runners to include in the report. |
| `toDate` | [`ISO8601Date`](#iso8601date) | UTC end date of the period to report on. " \ "Defaults to the end of the month specified by `fromDate`. |
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 10dbfdad88e..2483a1344ff 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -106,7 +106,7 @@ Before starting the flow, generate the `STATE`, the `CODE_VERIFIER` and the `COD
- The `CODE_VERIFIER` is a random string, between 43 and 128 characters in length,
which use the characters `A-Z`, `a-z`, `0-9`, `-`, `.`, `_`, and `~`.
- The `CODE_CHALLENGE` is an URL-safe base64-encoded string of the SHA256 hash of the
- `CODE_VERIFIER`
+ `CODE_VERIFIER`.
- The SHA256 hash must be in binary format before encoding.
- In Ruby, you can set that up with `Base64.urlsafe_encode64(Digest::SHA256.digest(CODE_VERIFIER), padding: false)`.
- For reference, a `CODE_VERIFIER` string of `ks02i3jdikdo2k0dkfodf3m39rjfjsdk0wk349rj3jrhf` when hashed
diff --git a/doc/development/rails_update.md b/doc/development/rails_update.md
index d00a1681e76..277af8d4092 100644
--- a/doc/development/rails_update.md
+++ b/doc/development/rails_update.md
@@ -18,10 +18,12 @@ We strive to run GitLab using the latest Rails releases to benefit from performa
1. Check the [Upgrading Ruby on Rails](https://guides.rubyonrails.org/upgrading_ruby_on_rails.html) guide and prepare the application for the upcoming changes.
1. Update the `rails` gem version in `Gemfile`.
-1. Run `bundle update rails`.
-1. Run the update task `rake rails:update`.
+1. Run `bundle update --conservative rails`.
+1. For major and minor version updates, run `bin/rails app:update` and check if any of the suggested changes should be applied.
1. Update the `activesupport` version in `qa/Gemfile`.
1. Run `bundle update --conservative activesupport` in the `qa` folder.
+1. Update the `activerecord_version` version in `vendor/gems/attr_encrypted/attr_encrypted.gemspec`.
+1. Run `bundle update --conservative activerecord` in the `vendor/gems/attr_encrypted` folder.
1. Resolve any Bundler conflicts.
1. Ensure that `@rails/ujs` and `@rails/actioncable` npm packages match the new rails version in [`package.json`](https://gitlab.com/gitlab-org/gitlab/blob/master/package.json).
1. Run `yarn patch-package @rails/ujs` after updating this to ensure our local patch file version matches.
diff --git a/doc/user/group/import/direct_transfer_migrations.md b/doc/user/group/import/direct_transfer_migrations.md
index 6e4809ce39c..1b613e6c4e0 100644
--- a/doc/user/group/import/direct_transfer_migrations.md
+++ b/doc/user/group/import/direct_transfer_migrations.md
@@ -114,6 +114,10 @@ role.
1. The **Status** column shows the import status of each group. If you leave the page open, it updates in real-time.
1. After a group has been imported, select its GitLab path to open its GitLab URL.
+WARNING:
+Importing groups with projects is in [beta](../../../policy/experiment-beta-support.md#beta). This feature is not
+ready for production use.
+
## Group import history
> - **Partially completed** status [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/394727) in GitLab 16.7.
diff --git a/doc/user/group/import/index.md b/doc/user/group/import/index.md
index 577ccfb78e9..9aa11a86e3c 100644
--- a/doc/user/group/import/index.md
+++ b/doc/user/group/import/index.md
@@ -43,13 +43,19 @@ Migrating groups by direct transfer copies the groups from one place to another.
- The subgroup of any existing top-level group.
- Another GitLab instance, including GitLab.com.
- In the [API](../../../api/bulk_imports.md), copy top-level groups and subgroups to these locations.
-- Copy groups with projects or without projects.
+- Copy groups with projects (in [beta](../../../policy/experiment-beta-support.md#beta) and not ready for production
+ use) or without projects. Copying projects with groups is available:
+ - On GitLab.com by default.
Not all group and project resources are copied. See list of copied resources below:
- [Migrated group items](migrated_items.md#migrated-group-items).
- [Migrated project items](migrated_items.md#migrated-project-items).
+WARNING:
+Importing groups with projects is in [beta](../../../policy/experiment-beta-support.md#beta). This feature is not
+ready for production use.
+
We invite you to leave your feedback about migrating by direct transfer in
[the feedback issue](https://gitlab.com/gitlab-org/gitlab/-/issues/284495).
diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md
index debea3edb65..f801ff04fb0 100644
--- a/doc/user/project/issues/managing_issues.md
+++ b/doc/user/project/issues/managing_issues.md
@@ -460,7 +460,7 @@ To view all issues assigned to you:
Or:
- To use a [keyboard shortcut](../../shortcuts.md), press Shift + i.
-- On the left sidebar, at the top, select **Issues** (**{issues}**).
+- On the left sidebar, at the top, select **Assigned issues** (**{issues}**).
## Filter the list of issues
diff --git a/lib/gitlab/background_migration/backfill_project_relation_exports_project_id.rb b/lib/gitlab/background_migration/backfill_project_relation_exports_project_id.rb
new file mode 100644
index 00000000000..94171f1ae76
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_project_relation_exports_project_id.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ class BackfillProjectRelationExportsProjectId < BackfillDesiredShardingKeyJob
+ operation_name :backfill_project_relation_exports_project_id
+ feature_category :importers
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/backfill_terraform_state_versions_project_id.rb b/lib/gitlab/background_migration/backfill_terraform_state_versions_project_id.rb
new file mode 100644
index 00000000000..17e34af40ec
--- /dev/null
+++ b/lib/gitlab/background_migration/backfill_terraform_state_versions_project_id.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ class BackfillTerraformStateVersionsProjectId < BackfillDesiredShardingKeyJob
+ operation_name :backfill_terraform_state_versions_project_id
+ feature_category :infrastructure_as_code
+ end
+ end
+end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9b8903c2db7..2bfd33347f1 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7151,7 +7151,7 @@ msgstr ""
msgid "Assigned %{reviewer_users_sentence} as %{reviewer_text}."
msgstr ""
-msgid "Assigned Issues"
+msgid "Assigned issues"
msgstr ""
msgid "Assigned merge requests"
@@ -9671,6 +9671,9 @@ msgstr ""
msgid "BulkImport|Import without projects"
msgstr ""
+msgid "BulkImport|Importing projects is a %{docsLinkStart}Beta%{docsLinkEnd} feature."
+msgstr ""
+
msgid "BulkImport|Importing the group failed."
msgstr ""
@@ -33297,6 +33300,9 @@ msgstr ""
msgid "MlModelRegistry|Failed to load model with error: %{message}"
msgstr ""
+msgid "MlModelRegistry|File \"%{name}\" is %{size}. It is larger than max allowed size of %{maxAllowedFileSize}"
+msgstr ""
+
msgid "MlModelRegistry|For example 1.0.0"
msgstr ""
@@ -33422,6 +33428,9 @@ msgid_plural "MlModelRegistry|ยท %d versions"
msgstr[0] ""
msgstr[1] ""
+msgid "Mlmodelregistry|Provide the max allowed file size"
+msgstr ""
+
msgid "Mock an external CI integration."
msgstr ""
@@ -60846,6 +60855,15 @@ msgstr ""
msgid "Your CI runner usage CSV export containing the top %{exported_objects} has been added to this email as an attachment."
msgstr ""
+msgid "Your CI runner usage CSV export containing the top %{exported_objects} in the \"%{full_path}\" group has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your CI runner usage CSV export for the \"%{full_path}\" project has been added to this email as an attachment."
+msgstr ""
+
+msgid "Your CI runner usage CSV export of the top %{exported_objects} has been added to this email as an attachment."
+msgstr ""
+
msgid "Your CI/CD configuration syntax is invalid. Select the Validate tab for more details."
msgstr ""
@@ -60858,9 +60876,6 @@ msgstr ""
msgid "Your CSV export of %{exported_objects} from project %{project_name} (%{project_url}) has been added to this email as an attachment."
msgstr ""
-msgid "Your CSV export of the top %{exported_objects} has been added to this email as an attachment."
-msgstr ""
-
msgid "Your CSV export request has succeeded. The result will be emailed to %{email}."
msgstr ""
diff --git a/spec/features/dashboard/issuables_counter_spec.rb b/spec/features/dashboard/issuables_counter_spec.rb
index d34f8cb3e18..4035c9e2f20 100644
--- a/spec/features/dashboard/issuables_counter_spec.rb
+++ b/spec/features/dashboard/issuables_counter_spec.rb
@@ -55,7 +55,7 @@ RSpec.describe 'Navigation bar counter', :use_clean_rails_memory_store_caching,
expect(dashboard_count).to have_content(count)
within_testid('super-sidebar') do
- expect(page).to have_link("Issues #{count}")
+ expect(page).to have_link("Assigned issues #{count}")
end
end
diff --git a/spec/features/dashboard/shortcuts_spec.rb b/spec/features/dashboard/shortcuts_spec.rb
index ccd7224a9be..239999da3c7 100644
--- a/spec/features/dashboard/shortcuts_spec.rb
+++ b/spec/features/dashboard/shortcuts_spec.rb
@@ -16,7 +16,7 @@ RSpec.describe 'Dashboard shortcuts', :js, feature_category: :shared do
it 'navigate to tabs' do
find('body').send_keys([:shift, 'I'])
- check_page_title('Issues')
+ check_page_title('Assigned issues')
find('body').send_keys([:shift, 'M'])
diff --git a/spec/frontend/ml/model_registry/apps/index_ml_models_spec.js b/spec/frontend/ml/model_registry/apps/index_ml_models_spec.js
index 10c3f6d1dd1..e9cb3865e90 100644
--- a/spec/frontend/ml/model_registry/apps/index_ml_models_spec.js
+++ b/spec/frontend/ml/model_registry/apps/index_ml_models_spec.js
@@ -21,6 +21,7 @@ Vue.use(VueApollo);
const defaultProps = {
projectPath: 'path/to/project',
canWriteModelRegistry: false,
+ maxAllowedFileSize: 99999,
};
describe('ml/model_registry/apps/index_ml_models', () => {
diff --git a/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js b/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js
index a380914f770..21e01d9405a 100644
--- a/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js
+++ b/spec/frontend/ml/model_registry/apps/show_ml_model_spec.js
@@ -89,6 +89,7 @@ describe('ml/model_registry/apps/show_ml_model', () => {
indexModelsPath: 'index/path',
mlflowTrackingUrl: 'path/to/tracking',
canWriteModelRegistry,
+ maxAllowedFileSize: 99999,
},
stubs: { GlTab, DeleteModel, LoadOrErrorOrShow },
});
diff --git a/spec/frontend/ml/model_registry/apps/show_ml_model_version_spec.js b/spec/frontend/ml/model_registry/apps/show_ml_model_version_spec.js
index cd2b0095215..7e0afa9ae95 100644
--- a/spec/frontend/ml/model_registry/apps/show_ml_model_version_spec.js
+++ b/spec/frontend/ml/model_registry/apps/show_ml_model_version_spec.js
@@ -60,6 +60,7 @@ describe('ml/model_registry/apps/show_model_version.vue', () => {
canWriteModelRegistry: true,
importPath: 'path/to/import',
modelPath: 'path/to/model',
+ maxAllowedFileSize: 99999,
},
apolloProvider,
stubs: {
diff --git a/spec/frontend/ml/model_registry/components/import_artifact_zone_spec.js b/spec/frontend/ml/model_registry/components/import_artifact_zone_spec.js
index 9374920e9d9..0dd29b3b55b 100644
--- a/spec/frontend/ml/model_registry/components/import_artifact_zone_spec.js
+++ b/spec/frontend/ml/model_registry/components/import_artifact_zone_spec.js
@@ -13,6 +13,8 @@ jest.mock('~/ml/model_registry/services/upload_model', () => ({
describe('ImportArtifactZone', () => {
let wrapper;
+ const provide = { maxAllowedFileSize: 99999 };
+
const file = { name: 'file.txt', size: 1024 };
const initialProps = {
path: 'some/path',
@@ -32,6 +34,7 @@ describe('ImportArtifactZone', () => {
propsData: {
...initialProps,
},
+ provide,
});
});
@@ -71,6 +74,7 @@ describe('ImportArtifactZone', () => {
},
importPath: 'some/path',
subfolder: '',
+ maxAllowedFileSize: 99999,
});
});
@@ -96,6 +100,7 @@ describe('ImportArtifactZone', () => {
propsData: {
...initialProps,
},
+ provide,
stubs: {
GlFormInputGroup,
},
@@ -128,6 +133,7 @@ describe('ImportArtifactZone', () => {
},
importPath: 'some/path',
subfolder: 'action',
+ maxAllowedFileSize: 99999,
});
});
});
@@ -138,6 +144,7 @@ describe('ImportArtifactZone', () => {
propsData: {
...initialProps,
},
+ provide,
});
});
@@ -169,6 +176,7 @@ describe('ImportArtifactZone', () => {
...initialProps,
submitOnSelect: false,
},
+ provide,
});
});
@@ -188,6 +196,7 @@ describe('ImportArtifactZone', () => {
...initialProps,
path: null,
},
+ provide,
});
});
diff --git a/spec/frontend/ml/model_registry/components/model_create_spec.js b/spec/frontend/ml/model_registry/components/model_create_spec.js
index 3b769df4dae..b13ebb3b084 100644
--- a/spec/frontend/ml/model_registry/components/model_create_spec.js
+++ b/spec/frontend/ml/model_registry/components/model_create_spec.js
@@ -1,11 +1,12 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import { GlAlert, GlModal } from '@gitlab/ui';
+import { GlModal } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { visitUrl } from '~/lib/utils/url_utility';
import ModelCreate from '~/ml/model_registry/components/model_create.vue';
import ImportArtifactZone from '~/ml/model_registry/components/import_artifact_zone.vue';
+import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
import { uploadModel } from '~/ml/model_registry/services/upload_model';
import createModelMutation from '~/ml/model_registry/graphql/mutations/create_model.mutation.graphql';
import createModelVersionMutation from '~/ml/model_registry/graphql/mutations/create_model_version.mutation.graphql';
@@ -29,6 +30,8 @@ describe('ModelCreate', () => {
let wrapper;
let apolloProvider;
+ const file = { name: 'file.txt', size: 1024 };
+
beforeEach(() => {
jest.spyOn(Sentry, 'captureException').mockImplementation();
});
@@ -54,8 +57,12 @@ describe('ModelCreate', () => {
},
provide: {
projectPath: 'some/project',
+ maxAllowedFileSize: 99999,
},
apolloProvider,
+ stubs: {
+ ImportArtifactZone,
+ },
});
};
@@ -65,8 +72,9 @@ describe('ModelCreate', () => {
const findDescriptionInput = () => wrapper.findByTestId('descriptionId');
const findVersionDescriptionInput = () => wrapper.findByTestId('versionDescriptionId');
const findImportArtifactZone = () => wrapper.findComponent(ImportArtifactZone);
+ const zone = () => wrapper.findComponent(UploadDropzone);
const findGlModal = () => wrapper.findComponent(GlModal);
- const findGlAlert = () => wrapper.findComponent(GlAlert);
+ const findGlAlert = () => wrapper.findByTestId('modal-create-alert');
const submitForm = async () => {
findGlModal().vm.$emit('primary', new Event('primary'));
await waitForPromises();
@@ -181,6 +189,8 @@ describe('ModelCreate', () => {
findVersionInput().vm.$emit('input', '1.0.0');
findDescriptionInput().vm.$emit('input', 'My model description');
findVersionDescriptionInput().vm.$emit('input', 'My version description');
+ await Vue.nextTick();
+ zone().vm.$emit('change', file);
jest.spyOn(apolloProvider.defaultClient, 'mutate');
await submitForm();
@@ -215,9 +225,10 @@ describe('ModelCreate', () => {
it('Uploads a file mutation upon confirm', () => {
expect(uploadModel).toHaveBeenCalledWith({
- file: null,
+ file,
importPath: '/api/v4/projects/1/packages/ml_models/1/files/',
subfolder: '',
+ maxAllowedFileSize: 99999,
});
});
@@ -256,6 +267,8 @@ describe('ModelCreate', () => {
findNameInput().vm.$emit('input', 'gpt-alice-1');
findVersionInput().vm.$emit('input', '1.0.0');
findVersionDescriptionInput().vm.$emit('input', 'My version description');
+ await Vue.nextTick();
+ zone().vm.$emit('change', file);
await submitForm();
});
@@ -304,6 +317,8 @@ describe('ModelCreate', () => {
findVersionInput().vm.$emit('input', '1.0.0');
findDescriptionInput().vm.$emit('input', 'My model description');
findVersionDescriptionInput().vm.$emit('input', 'My version description');
+ await Vue.nextTick();
+ zone().vm.$emit('change', file);
uploadModel.mockRejectedValueOnce('Artifact import error.');
await submitForm();
});
@@ -313,6 +328,16 @@ describe('ModelCreate', () => {
await submitForm(); // retry submit
expect(visitUrl).toHaveBeenCalledWith('/some/project/-/ml/models/1/versions/1');
});
+
+ it('Uploads a file mutation upon confirm', async () => {
+ await submitForm(); // retry submit
+ expect(uploadModel).toHaveBeenCalledWith({
+ file,
+ importPath: '/api/v4/projects/1/packages/ml_models/1/files/',
+ subfolder: '',
+ maxAllowedFileSize: 99999,
+ });
+ });
});
describe('Failed flow without version', () => {
diff --git a/spec/frontend/ml/model_registry/components/model_detail_spec.js b/spec/frontend/ml/model_registry/components/model_detail_spec.js
index 2c3c59241fc..ad51626a15f 100644
--- a/spec/frontend/ml/model_registry/components/model_detail_spec.js
+++ b/spec/frontend/ml/model_registry/components/model_detail_spec.js
@@ -11,6 +11,7 @@ let wrapper;
const createWrapper = (modelProp = model) => {
wrapper = shallowMountExtended(ModelDetail, {
propsData: { model: modelProp },
+ provide: { maxAllowedFileSize: 99999 },
stubs: { GlTab },
});
};
diff --git a/spec/frontend/ml/model_registry/components/model_version_create_spec.js b/spec/frontend/ml/model_registry/components/model_version_create_spec.js
index 6ab37352ac9..7045e8be81d 100644
--- a/spec/frontend/ml/model_registry/components/model_version_create_spec.js
+++ b/spec/frontend/ml/model_registry/components/model_version_create_spec.js
@@ -6,6 +6,7 @@ import * as Sentry from '~/sentry/sentry_browser_wrapper';
import { visitUrl } from '~/lib/utils/url_utility';
import ModelVersionCreate from '~/ml/model_registry/components/model_version_create.vue';
import ImportArtifactZone from '~/ml/model_registry/components/import_artifact_zone.vue';
+import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue';
import { uploadModel } from '~/ml/model_registry/services/upload_model';
import createModelVersionMutation from '~/ml/model_registry/graphql/mutations/create_model_version.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
@@ -28,6 +29,8 @@ describe('ModelVersionCreate', () => {
let wrapper;
let apolloProvider;
+ const file = { name: 'file.txt', size: 1024 };
+
beforeEach(() => {
jest.spyOn(Sentry, 'captureException').mockImplementation();
});
@@ -45,11 +48,15 @@ describe('ModelVersionCreate', () => {
wrapper = shallowMountExtended(ModelVersionCreate, {
provide: {
projectPath: 'some/project',
+ maxAllowedFileSize: 99999,
},
propsData: {
modelGid: 'gid://gitlab/Ml::Model/1',
},
apolloProvider,
+ stubs: {
+ UploadDropzone,
+ },
});
};
@@ -57,6 +64,7 @@ describe('ModelVersionCreate', () => {
const findVersionInput = () => wrapper.findByTestId('versionId');
const findDescriptionInput = () => wrapper.findByTestId('descriptionId');
const findImportArtifactZone = () => wrapper.findComponent(ImportArtifactZone);
+ const zone = () => wrapper.findComponent(UploadDropzone);
const findGlModal = () => wrapper.findComponent(GlModal);
const findGlAlert = () => wrapper.findComponent(GlAlert);
const submitForm = async () => {
@@ -128,6 +136,7 @@ describe('ModelVersionCreate', () => {
createWrapper();
findVersionInput().vm.$emit('input', '1.0.0');
findDescriptionInput().vm.$emit('input', 'My model version description');
+ zone().vm.$emit('change', file);
jest.spyOn(apolloProvider.defaultClient, 'mutate');
await submitForm();
@@ -149,11 +158,13 @@ describe('ModelVersionCreate', () => {
it('Uploads a file mutation upon confirm', () => {
expect(uploadModel).toHaveBeenCalledWith({
- file: null,
+ file,
importPath: '/api/v4/projects/1/packages/ml_models/1/files/',
subfolder: '',
+ maxAllowedFileSize: 99999,
});
});
+
it('Visits the model versions page upon successful create mutation', async () => {
createWrapper();
@@ -194,18 +205,29 @@ describe('ModelVersionCreate', () => {
describe('Failed flow with file upload retried', () => {
beforeEach(async () => {
createWrapper();
+ findVersionInput().vm.$emit('input', '1.0.0');
+ zone().vm.$emit('change', file);
uploadModel.mockRejectedValueOnce('Artifact import error.');
await submitForm();
});
it('Visits the model versions page upon successful create mutation', async () => {
- expect(findGlAlert().text()).toBe('Artifact import error.');
-
await submitForm();
expect(visitUrl).toHaveBeenCalledWith('/some/project/-/ml/models/1/versions/1');
});
+
+ it('Uploads the model upon retry', async () => {
+ await submitForm();
+
+ expect(uploadModel).toHaveBeenCalledWith({
+ file,
+ importPath: '/api/v4/projects/1/packages/ml_models/1/files/',
+ subfolder: '',
+ maxAllowedFileSize: 99999,
+ });
+ });
});
});
});
diff --git a/spec/frontend/ml/model_registry/components/model_version_detail_spec.js b/spec/frontend/ml/model_registry/components/model_version_detail_spec.js
index 3225ffba3aa..7e3a1d1591e 100644
--- a/spec/frontend/ml/model_registry/components/model_version_detail_spec.js
+++ b/spec/frontend/ml/model_registry/components/model_version_detail_spec.js
@@ -35,6 +35,7 @@ const createWrapper = (modelVersion = modelVersionWithCandidate, props = {}, pro
projectPath: 'path/to/project',
canWriteModelRegistry: true,
importPath: 'path/to/import',
+ maxAllowedFileSize: 99999,
...provide,
},
});
diff --git a/spec/frontend/ml/model_registry/services/upload_model_spec.js b/spec/frontend/ml/model_registry/services/upload_model_spec.js
index 096b56ebf46..e3173643325 100644
--- a/spec/frontend/ml/model_registry/services/upload_model_spec.js
+++ b/spec/frontend/ml/model_registry/services/upload_model_spec.js
@@ -4,6 +4,8 @@ import { uploadModel } from '~/ml/model_registry/services/upload_model';
describe('uploadModel', () => {
const importPath = 'some/path';
const file = { name: 'file.txt', size: 1024 };
+ const largeFile = { name: 'file.txt', size: 2024 };
+ const maxAllowedFileSize = 2000;
let axiosMock;
beforeEach(() => {
@@ -18,7 +20,7 @@ describe('uploadModel', () => {
it('should upload a file to the specified import path', async () => {
const filePath = `${importPath}/${encodeURIComponent(file.name)}`;
- await uploadModel({ importPath, file });
+ await uploadModel({ importPath, file, maxAllowedFileSize });
expect(axiosMock).toHaveBeenCalledTimes(1);
expect(axiosMock).toHaveBeenCalledWith(filePath, expect.any(FormData), {
@@ -32,7 +34,7 @@ describe('uploadModel', () => {
const subfolder = 'action';
const filePath = `${importPath}/action/${encodeURIComponent(file.name)}`;
- await uploadModel({ importPath, file, subfolder });
+ await uploadModel({ importPath, file, subfolder, maxAllowedFileSize });
expect(axiosMock).toHaveBeenCalledTimes(1);
expect(axiosMock).toHaveBeenCalledWith(filePath, expect.any(FormData), {
@@ -47,4 +49,12 @@ describe('uploadModel', () => {
expect(axiosMock).not.toHaveBeenCalled();
});
+
+ it('should raise an error for large files', async () => {
+ await expect(
+ uploadModel({ importPath: 'some/path', file: largeFile, maxAllowedFileSize }),
+ ).rejects.toThrow(
+ new Error('File "file.txt" is 1.98 KiB. It is larger than max allowed size of 1.95 KiB'),
+ );
+ });
});
diff --git a/spec/frontend/super_sidebar/components/user_bar_spec.js b/spec/frontend/super_sidebar/components/user_bar_spec.js
index 5909ee663cb..a56bedb619c 100644
--- a/spec/frontend/super_sidebar/components/user_bar_spec.js
+++ b/spec/frontend/super_sidebar/components/user_bar_spec.js
@@ -106,7 +106,7 @@ describe('UserBar component', () => {
const isuesCounter = findIssuesCounter();
expect(isuesCounter.props('count')).toBe(userCounts.assigned_issues);
expect(isuesCounter.props('href')).toBe(mockSidebarData.issues_dashboard_path);
- expect(isuesCounter.props('label')).toBe(__('Issues'));
+ expect(isuesCounter.props('label')).toBe(__('Assigned issues'));
expect(isuesCounter.attributes('data-track-action')).toBe('click_link');
expect(isuesCounter.attributes('data-track-label')).toBe('issues_link');
expect(isuesCounter.attributes('data-track-property')).toBe('nav_core_menu');
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 5306b86d08f..9f584971bfd 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -36,7 +36,7 @@ RSpec.describe PreferencesHelper do
{ text: "Followed Users' Activity", value: 'followed_user_activity' },
{ text: "Your Groups", value: 'groups' },
{ text: "Your To-Do List", value: 'todos' },
- { text: "Assigned Issues", value: 'issues' },
+ { text: "Assigned issues", value: 'issues' },
{ text: "Assigned merge requests", value: 'merge_requests' }
]
end
diff --git a/spec/helpers/projects/ml/model_registry_helper_spec.rb b/spec/helpers/projects/ml/model_registry_helper_spec.rb
index e3021cf3707..41f4f7dfa43 100644
--- a/spec/helpers/projects/ml/model_registry_helper_spec.rb
+++ b/spec/helpers/projects/ml/model_registry_helper_spec.rb
@@ -17,6 +17,7 @@ RSpec.describe Projects::Ml::ModelRegistryHelper, feature_category: :mlops do
'projectPath' => project.full_path,
'createModelPath' => "/#{project.full_path}/-/ml/models/new",
'canWriteModelRegistry' => true,
+ 'maxAllowedFileSize' => 10737418240,
'mlflowTrackingUrl' => "http://localhost/api/v4/projects/#{project.id}/ml/mlflow/"
})
end
@@ -47,6 +48,7 @@ RSpec.describe Projects::Ml::ModelRegistryHelper, feature_category: :mlops do
'projectPath' => project.full_path,
'indexModelsPath' => "/#{project.full_path}/-/ml/models",
'canWriteModelRegistry' => true,
+ 'maxAllowedFileSize' => 10737418240,
'mlflowTrackingUrl' => "http://localhost/api/v4/projects/#{project.id}/ml/mlflow/",
'modelId' => model.id,
'modelName' => 'cool_model'
@@ -86,6 +88,7 @@ RSpec.describe Projects::Ml::ModelRegistryHelper, feature_category: :mlops do
"modelName" => model_version.name,
"versionName" => model_version.version,
"canWriteModelRegistry" => true,
+ 'maxAllowedFileSize' => 10737418240,
"importPath" => "/api/v4/projects/#{project.id}/packages/ml_models/#{model_version.id}/files/",
"modelPath" => "/#{project.full_path}/-/ml/models/1"
})
diff --git a/spec/lib/gitlab/background_migration/backfill_project_relation_exports_project_id_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_relation_exports_project_id_spec.rb
new file mode 100644
index 00000000000..afc0f17999b
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_project_relation_exports_project_id_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillProjectRelationExportsProjectId,
+ feature_category: :importers,
+ schema: 20240605113246 do
+ include_examples 'desired sharding key backfill job' do
+ let(:batch_table) { :project_relation_exports }
+ let(:backfill_column) { :project_id }
+ let(:backfill_via_table) { :project_export_jobs }
+ let(:backfill_via_column) { :project_id }
+ let(:backfill_via_foreign_key) { :project_export_job_id }
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_terraform_state_versions_project_id_spec.rb b/spec/lib/gitlab/background_migration/backfill_terraform_state_versions_project_id_spec.rb
new file mode 100644
index 00000000000..bdeb56dcb5e
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/backfill_terraform_state_versions_project_id_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::BackfillTerraformStateVersionsProjectId,
+ feature_category: :infrastructure_as_code,
+ schema: 20240605132806 do
+ include_examples 'desired sharding key backfill job' do
+ let(:batch_table) { :terraform_state_versions }
+ let(:backfill_column) { :project_id }
+ let(:backfill_via_table) { :terraform_states }
+ let(:backfill_via_column) { :project_id }
+ let(:backfill_via_foreign_key) { :terraform_state_id }
+ end
+end
diff --git a/spec/migrations/20240605113250_queue_backfill_project_relation_exports_project_id_spec.rb b/spec/migrations/20240605113250_queue_backfill_project_relation_exports_project_id_spec.rb
new file mode 100644
index 00000000000..e48be5a2249
--- /dev/null
+++ b/spec/migrations/20240605113250_queue_backfill_project_relation_exports_project_id_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe QueueBackfillProjectRelationExportsProjectId, feature_category: :importers do
+ let!(:batched_migration) { described_class::MIGRATION }
+
+ it 'schedules a new batched migration' do
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(batched_migration).not_to have_scheduled_batched_migration
+ }
+
+ migration.after -> {
+ expect(batched_migration).to have_scheduled_batched_migration(
+ table_name: :project_relation_exports,
+ column_name: :id,
+ interval: described_class::DELAY_INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE,
+ gitlab_schema: :gitlab_main_cell,
+ job_arguments: [
+ :project_id,
+ :project_export_jobs,
+ :project_id,
+ :project_export_job_id
+ ]
+ )
+ }
+ end
+ end
+end
diff --git a/spec/migrations/20240605132810_queue_backfill_terraform_state_versions_project_id_spec.rb b/spec/migrations/20240605132810_queue_backfill_terraform_state_versions_project_id_spec.rb
new file mode 100644
index 00000000000..dce3aea7d7e
--- /dev/null
+++ b/spec/migrations/20240605132810_queue_backfill_terraform_state_versions_project_id_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_migration!
+
+RSpec.describe QueueBackfillTerraformStateVersionsProjectId, feature_category: :infrastructure_as_code do
+ let!(:batched_migration) { described_class::MIGRATION }
+
+ it 'schedules a new batched migration' do
+ reversible_migration do |migration|
+ migration.before -> {
+ expect(batched_migration).not_to have_scheduled_batched_migration
+ }
+
+ migration.after -> {
+ expect(batched_migration).to have_scheduled_batched_migration(
+ table_name: :terraform_state_versions,
+ column_name: :id,
+ interval: described_class::DELAY_INTERVAL,
+ batch_size: described_class::BATCH_SIZE,
+ sub_batch_size: described_class::SUB_BATCH_SIZE,
+ gitlab_schema: :gitlab_main_cell,
+ job_arguments: [
+ :project_id,
+ :terraform_states,
+ :project_id,
+ :terraform_state_id
+ ]
+ )
+ }
+ end
+ end
+end
diff --git a/spec/rails_autoload.rb b/spec/rails_autoload.rb
index d3518acf8b2..865b6790260 100644
--- a/spec/rails_autoload.rb
+++ b/spec/rails_autoload.rb
@@ -48,9 +48,22 @@ require_relative '../config/initializers/0_inject_enterprise_edition_module'
require_relative '../config/initializers_before_autoloader/000_inflections'
require_relative '../config/initializers_before_autoloader/004_zeitwerk'
-Rails.autoloaders.each do |autoloader|
- autoloader.push_dir('lib')
- autoloader.push_dir('ee/lib') if Gitlab.ee?
- autoloader.push_dir('jh/lib') if Gitlab.jh?
- autoloader.setup
+# We wrap this bit of logic in a module to avoid polluting the global namespace with a local variable and methods
+module AutoloadersSetup
+ def self.dir_already_autoloaded?(autoloaded_dirs, dir)
+ autoloaded_dirs.any? { File.expand_path(dir, __dir__) }
+ end
+
+ def self.setup_autoloaders
+ autoloaded_dirs = [] # NOTE: can't use Rails.autoloaders.each_with_object, it doesn't work, so we need a local var.
+ Rails.autoloaders.each do |autoloader|
+ autoloader.push_dir('lib') unless dir_already_autoloaded?(autoloaded_dirs, "../lib")
+ autoloader.push_dir('ee/lib') if Gitlab.ee? && !dir_already_autoloaded?(autoloaded_dirs, "../ee/lib")
+ autoloader.push_dir('jh/lib') if Gitlab.jh? && !dir_already_autoloaded?(autoloaded_dirs, "../jh/lib")
+ autoloader.setup
+ autoloaded_dirs += autoloader.dirs
+ end
+ end
end
+
+AutoloadersSetup.setup_autoloaders
diff --git a/spec/support/shared_examples/features/work_items_shared_examples.rb b/spec/support/shared_examples/features/work_items_shared_examples.rb
index 025b721ff9b..8de581f1b2d 100644
--- a/spec/support/shared_examples/features/work_items_shared_examples.rb
+++ b/spec/support/shared_examples/features/work_items_shared_examples.rb
@@ -735,13 +735,13 @@ RSpec.shared_examples 'work items time tracking' do
click_button '3d'
expect(page).to have_css 'h2', text: 'Time tracking report'
- expect(page).to have_text '1d Sidney Jones1 First summary'
- expect(page).to have_text '2d Sidney Jones1 Second summary'
+ expect(page).to have_text "1d #{user.name} First summary"
+ expect(page).to have_text "2d #{user.name} Second summary"
click_button 'Delete time spent', match: :first
- expect(page).to have_text '1d Sidney Jones1 First summary'
- expect(page).not_to have_text '2d Sidney Jones1 Second summary'
+ expect(page).to have_text "1d #{user.name} First summary"
+ expect(page).not_to have_text "2d #{user.name} Second summary"
click_button 'Close'
diff --git a/workhorse/testdata/localhost.crt b/workhorse/testdata/localhost.crt
index bee60e42e00..eea46eb34f4 100644
--- a/workhorse/testdata/localhost.crt
+++ b/workhorse/testdata/localhost.crt
@@ -1,27 +1,21 @@
-----BEGIN CERTIFICATE-----
-MIIEjjCCAvagAwIBAgIQC2au+A/aGQ2Z21O0wVoEwjANBgkqhkiG9w0BAQsFADCB
-pTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMT0wOwYDVQQLDDRpZ29y
-ZHJvemRvdkBJZ29ycy1NYWNCb29rLVByby0yLmxvY2FsIChJZ29yIERyb3pkb3Yp
-MUQwQgYDVQQDDDtta2NlcnQgaWdvcmRyb3pkb3ZASWdvcnMtTWFjQm9vay1Qcm8t
-Mi5sb2NhbCAoSWdvciBEcm96ZG92KTAeFw0yMjAzMDcwNDMxMjRaFw0yNDA2MDcw
-NDMxMjRaMGgxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0
-ZTE9MDsGA1UECww0aWdvcmRyb3pkb3ZASWdvcnMtTWFjQm9vay1Qcm8tMi5sb2Nh
-bCAoSWdvciBEcm96ZG92KTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
-AMJ8ofGdcnenVRtNGViF4oxPv+CCFA6D2nfsjkJG8kmO6WW7VlbhJYxCMAuyFF1F
-b2UI2rrTFL8Aeq1KxeQzdrb3cpCquVH/UQ00G4ply28XVPRdbIyLQvOThMEeLL6v
-6gb4edL5oZmo/vWhdQxv0NGt282PAEt+bjnbdl28on8WVzmsw/m0nZ2BVWke+oUM
-krfsbyFaZj7aW8w0dNeK25ANy/Ldx55ENRDquphwYHDnpFOQpkHo5nPuoms5j2Sf
-GW3u3hgeFhRrFjqDstU3OKdA4AdHntDjl0gHm35w1m8PXiql/3EpkEMMx5ixQAqM
-cMZ7VVzy0HIjqsjdJZpzjx8CAwEAAaN2MHQwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud
-JQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFKTVZ2JsYLGJOP+UX0AwGO/81Kab
-MCwGA1UdEQQlMCOCCWxvY2FsaG9zdIcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATAN
-BgkqhkiG9w0BAQsFAAOCAYEAkGntoogSlhukGqTNbTXN9T/gXLtx9afWlgcBEafF
-MYQoJ1DOwXoYCQkMsxE0xWUyLDTpvjzfKkkyQwWzTwcYqRHOKafKYVSvENU5oaDY
-c2nk32SfkcF6bqJ50uBlFMEvKFExU1U+YSJhuEH/iqT9sSd52uwmnB0TJhSOc3J/
-1ZapKM2G71ezi8OyizwlwDJAwQ37CqrYS2slVO6Cy8zJ1l/ZsZ+kxRb+ME0LREI0
-J/rFTo9A6iyuXeBQ2jiRUrC6pmmbUQbVSjROx4RSmWoI/58/VnuZBY9P62OAOgUv
-pukfAbh3SUjN5++m4Py7WjP/y+L2ILPOFtxTY+CQPWQ5Hbff8iMB4NNfutdU1wSS
-CzXT1zWbU12kXod80wkMqWvNb3yU5spqXV6WYhOHiDIyqpPIqp5/i93Ck3Hd6/BQ
-DYlNOQsVHdSjWzNw9UubjpatiFqMK4hvJZE0haoLlmfDeZeqWk9oAuuCibLJGPg4
-TQri+lKgi0e76ynUr1zP1xUR
+MIIDfDCCAmSgAwIBAgIUa/KdQW7BbWxtDFUBbRpFxEvR2wUwDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0yNDA2MDcwOTI2MjlaFw0zMjA4
+MjQwOTI2MjlaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggEiMA0GCSqGSIb3DQEB
+AQUAA4IBDwAwggEKAoIBAQDCfKHxnXJ3p1UbTRlYheKMT7/gghQOg9p37I5CRvJJ
+jullu1ZW4SWMQjALshRdRW9lCNq60xS/AHqtSsXkM3a293KQqrlR/1ENNBuKZctv
+F1T0XWyMi0Lzk4TBHiy+r+oG+HnS+aGZqP71oXUMb9DRrdvNjwBLfm4523ZdvKJ/
+Flc5rMP5tJ2dgVVpHvqFDJK37G8hWmY+2lvMNHTXituQDcvy3ceeRDUQ6rqYcGBw
+56RTkKZB6OZz7qJrOY9knxlt7t4YHhYUaxY6g7LVNzinQOAHR57Q45dIB5t+cNZv
+D14qpf9xKZBDDMeYsUAKjHDGe1Vc8tByI6rI3SWac48fAgMBAAGjZDBiMB0GA1Ud
+DgQWBBQBEdAtdWtE9gHf3Dq1I02pE2gQGDAfBgNVHSMEGDAWgBQBEdAtdWtE9gHf
+3Dq1I02pE2gQGDAPBgNVHRMBAf8EBTADAQH/MA8GA1UdEQQIMAaHBH8AAAEwDQYJ
+KoZIhvcNAQELBQADggEBAGJeDycH50Olfcrm+nr8eOSMTf4Ehq2QRDZabFClp2sy
+HHGfqqbCtrBFpshI+T04H10ncUEM6GQR/iQ4+dWR3LppZWc4bQAIE4145n4jsEP1
+xwYgiSGRwx3Blzb6RlkTwHmaJpEw9mOSHMkt/0syicvgrPb/dEPrO26IhBhY3Me3
+H1tpGYQQW6fXx8nJNHrVmzlp9Be0N71wNBocD2nnmaHf8+m9CF7kQ28zXrawC5lz
+JRafQi4fxUQC01cGoJmva1wa9xr4LItVp6PcOowohh3x5mmtySYxYi9DVgHJRmci
+6ANK7CUbCaAydlv4EX3sz3YMEe35+e1n3I19wkHWqBA=
-----END CERTIFICATE-----