diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-09 12:11:06 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2023-02-09 12:11:06 +0000 |
commit | 6180f62ab34662c64103872b8352b25817b73a8d (patch) | |
tree | 2d7f30b845726fb0bff9ee191c11996c04c22c2c | |
parent | 2e2db606cc7547b445a11c367d8db6f5feb42443 (diff) | |
download | gitlab-ce-6180f62ab34662c64103872b8352b25817b73a8d.tar.gz |
Add latest changes from gitlab-org/gitlab@master
46 files changed, 566 insertions, 144 deletions
diff --git a/.gitlab/issue_templates/Release group - bug.md b/.gitlab/issue_templates/Release group - bug.md new file mode 100644 index 00000000000..f542041593f --- /dev/null +++ b/.gitlab/issue_templates/Release group - bug.md @@ -0,0 +1,43 @@ +## Summary + +<!-- Summarize the bug encountered concisely. --> + +## Steps to reproduce + +<!-- Describe how one can reproduce the issue - this is very important. Please use an ordered list. --> + +## What is the current *bug* behavior? + +<!-- Describe what actually happens. --> + +## What is the expected *correct* behavior? + +<!-- Describe what you should see instead. --> + +## Relevant logs and/or screenshots + +<!-- Paste any relevant logs - please use code blocks (```) to format console output, logs, and code + as it's tough to read otherwise. --> + +## Possible fixes + +<!-- If you can, link to the line of code that might be responsible for the problem. --> + +<!-- Default labels --> +/label ~"group::release" +/label ~"type::bug" +/label ~"workflow::planning breakdown" + +<!-- Optional labels --> +/label ~backend ~frontend ~UX + +<!-- https://about.gitlab.com/handbook/engineering/quality/issue-triage/#severity --> +/label ~"severity::3" ~"priority::3" + +<!-- If you already have an implementation plan, please fill in detail below https://about.gitlab.com/handbook/engineering/development/ops/release/planning/#weights --> +/weight X +/label ~"backend-weight::" ~"frontend-weight::" ~"workflow::ready for development" +## Implementation guide + +<!-- If the issue clear enough, consider inviting community contributors --> +/label ~"Seeking community contributions" ~"good for new contributors" diff --git a/.gitlab/issue_templates/Release group - feature.md b/.gitlab/issue_templates/Release group - feature.md new file mode 100644 index 00000000000..0a695dac2ad --- /dev/null +++ b/.gitlab/issue_templates/Release group - feature.md @@ -0,0 +1,26 @@ +## Problem to solve + +<!-- What problem do we solve? Try to define the who/what/why of the opportunity as a user story. For example, "As a (who), I want (what), so I can (why/value)." --> + +## Proposal + +<!-- Use this section to explain the feature and how it will work. It can be helpful to add technical details, design proposals, and links to related epics or issues. --> + +<!-- Default labels --> +/label ~"group::release" +/label ~"type::feature" +/label ~"workflow::problem validation" +/label ~UX + +<!-- Optional labels --> +/label ~backend ~frontend + +<!-- If you already have an implementation plan, please fill in detail below https://about.gitlab.com/handbook/engineering/development/ops/release/planning/#weights --> +/weight X +/label ~"backend-weight::" ~"frontend-weight::" ~"workflow::ready for development" +## Implementation guide + +<!-- If the issue clear enough, consider inviting community contributors --> +/label ~"Seeking community contributions" ~"good for new contributors" + + diff --git a/.gitlab/issue_templates/Release group - maintenance.md b/.gitlab/issue_templates/Release group - maintenance.md new file mode 100644 index 00000000000..aa87015a44a --- /dev/null +++ b/.gitlab/issue_templates/Release group - maintenance.md @@ -0,0 +1,18 @@ +## Background + +## Proposal + +<!-- Use this section to explain the feature and how it will work. It can be helpful to add technical details, design proposals, and links to related epics or issues. --> + +<!-- Set labels and other metadata --> +/label ~"group::release" +/label ~"type::maintenance" +/label ~"workflow::planning breakdown" + +<!-- If you already have an implementation plan, please fill in detail below https://about.gitlab.com/handbook/engineering/development/ops/release/planning/#weights --> +/weight X +/label ~"backend-weight::" ~"frontend-weight::" ~"workflow::ready for development" +## Implementation guide + +<!-- If the issue clear enough, consider inviting community contributors --> +/label ~"Seeking community contributions" ~"good for new contributors" diff --git a/.rubocop_todo/layout/argument_alignment.yml b/.rubocop_todo/layout/argument_alignment.yml index 5d46979d563..4e22c8a85a4 100644 --- a/.rubocop_todo/layout/argument_alignment.yml +++ b/.rubocop_todo/layout/argument_alignment.yml @@ -524,7 +524,6 @@ Layout/ArgumentAlignment: - 'app/graphql/types/snippets/blob_connection_type.rb' - 'app/graphql/types/snippets/blob_type.rb' - 'app/graphql/types/sort_enum.rb' - - 'app/graphql/types/subscription_type.rb' - 'app/graphql/types/task_completion_status.rb' - 'app/graphql/types/terraform/state_type.rb' - 'app/graphql/types/terraform/state_version_type.rb' @@ -791,10 +790,6 @@ Layout/ArgumentAlignment: - 'config/routes/unmatched_project.rb' - 'config/routes/uploads.rb' - 'config/routes/user.rb' - - 'db/migrate/20210604082145_create_external_status_checks_table.rb' - - 'db/migrate/20210609090856_add_expiry_id_ssh_key_notification_index.rb' - - 'db/migrate/20210709221051_create_work_item_types.rb' - - 'db/migrate/20210719182944_add_request_response_to_supporing_message.rb' - 'db/migrate/20210901065504_add_index_on_name_and_id_to_public_groups.rb' - 'db/migrate/20210910014741_add_dependency_proxy_ttl_group_policy_worker_capacity_to_application_settings.rb' - 'db/migrate/20211111112639_add_fk_compliance_violations_merge_request.rb' @@ -864,17 +859,6 @@ Layout/ArgumentAlignment: - 'db/migrate/20230124193917_add_index_for_protected_tag_create_access_levels.rb' - 'db/migrate/20230127151529_add_project_pointer_for_analytics_dashboard.rb' - 'db/migrate/20230127151531_change_dashboard_analytics_project_pointer_project_null.rb' - - 'db/post_migrate/20210611080951_fix_missing_traversal_ids.rb' - - 'db/post_migrate/20210622045705_finalize_events_bigint_conversion.rb' - - 'db/post_migrate/20210701141346_finalize_ci_builds_stage_id_bigint_conversion.rb' - - 'db/post_migrate/20210707210916_finalize_ci_stages_bigint_conversion.rb' - - 'db/post_migrate/20210708011426_finalize_ci_builds_metadata_bigint_conversion.rb' - - 'db/post_migrate/20210802043253_finalize_push_event_payloads_bigint_conversion_3.rb' - - 'db/post_migrate/20210804151444_prepare_indexes_for_ci_job_artifact_bigint_conversion.rb' - - 'db/post_migrate/20210804153307_prepare_indexes_for_tagging_bigint_conversion.rb' - - 'db/post_migrate/20210804154407_prepare_indexes_for_ci_stage_bigint_conversion.rb' - - 'db/post_migrate/20210817024335_prepare_indexes_for_events_bigint_conversion.rb' - - 'db/post_migrate/20210824174615_prepare_ci_builds_metadata_and_ci_build_async_indexes.rb' - 'db/post_migrate/20210921062820_add_image_location_index_to_vulnerability_occurrences.rb' - 'db/post_migrate/20211007093340_remove_analytics_snapshots_segment_id_column.rb' - 'db/post_migrate/20211021140426_remove_geo_upload_deprecated_fields.rb' @@ -1207,7 +1191,6 @@ Layout/ArgumentAlignment: - 'ee/app/graphql/types/pipeline_security_report_finding_type.rb' - 'ee/app/graphql/types/product_analytics/dashboard_type.rb' - 'ee/app/graphql/types/product_analytics/visualization_type.rb' - - 'ee/app/graphql/types/product_analytics/widget_type.rb' - 'ee/app/graphql/types/protected_environment_type.rb' - 'ee/app/graphql/types/protected_environments/approval_rule_for_summary_type.rb' - 'ee/app/graphql/types/protected_environments/approval_rule_type.rb' @@ -1343,7 +1326,6 @@ Layout/ArgumentAlignment: - 'ee/app/services/security/track_scan_service.rb' - 'ee/app/services/vulnerabilities/confirm_service.rb' - 'ee/app/services/vulnerabilities/findings/find_or_create_from_security_finding_service.rb' - - 'ee/app/services/vulnerabilities/security_finding/create_issue_service.rb' - 'ee/app/services/vulnerabilities/security_finding/create_merge_request_service.rb' - 'ee/app/services/vulnerabilities/starboard_vulnerability_resolve_service.rb' - 'ee/app/services/vulnerability_feedback/create_service.rb' @@ -1515,7 +1497,6 @@ Layout/ArgumentAlignment: - 'ee/spec/features/search/elastic/global_search_spec.rb' - 'ee/spec/features/search/elastic/group_search_spec.rb' - 'ee/spec/features/security/project/discover_spec.rb' - - 'ee/spec/features/trials/select_namespace_spec.rb' - 'ee/spec/features/uncompleted_learn_gitlab_link_spec.rb' - 'ee/spec/features/users/identity_verification_spec.rb' - 'ee/spec/finders/boards/milestones_finder_spec.rb' @@ -1546,6 +1527,7 @@ Layout/ArgumentAlignment: - 'ee/spec/graphql/types/dast/pre_scan_verification_type_spec.rb' - 'ee/spec/graphql/types/dast/profile_type_spec.rb' - 'ee/spec/graphql/types/dast_scanner_profile_type_spec.rb' + - 'ee/spec/graphql/types/pipeline_security_report_finding_type_spec.rb' - 'ee/spec/graphql/types/project_type_spec.rb' - 'ee/spec/graphql/types/vulnerability_type_spec.rb' - 'ee/spec/helpers/billing_plans_helper_spec.rb' @@ -1639,8 +1621,6 @@ Layout/ArgumentAlignment: - 'ee/spec/lib/incident_management/oncall_shift_generator_spec.rb' - 'ee/spec/lib/omni_auth/strategies/group_saml_spec.rb' - 'ee/spec/mailers/notify_spec.rb' - - 'ee/spec/migrations/add_non_null_constraint_for_escalation_rule_on_pending_alert_escalations_spec.rb' - - 'ee/spec/migrations/remove_schedule_and_status_null_constraints_from_pending_escalations_alert_spec.rb' - 'ee/spec/migrations/update_vulnerability_occurrences_location_spec.rb' - 'ee/spec/models/approval_wrapped_code_owner_rule_spec.rb' - 'ee/spec/models/ci/bridge_spec.rb' @@ -1664,8 +1644,8 @@ Layout/ArgumentAlignment: - 'ee/spec/models/ee/event_spec.rb' - 'ee/spec/models/ee/group_spec.rb' - 'ee/spec/models/ee/namespace_spec.rb' - - 'ee/spec/models/ee/user_spec.rb' - 'ee/spec/models/ee/project_spec.rb' + - 'ee/spec/models/ee/user_spec.rb' - 'ee/spec/models/epic_spec.rb' - 'ee/spec/models/geo/project_registry_spec.rb' - 'ee/spec/models/ldap_group_link_spec.rb' @@ -2051,7 +2031,6 @@ Layout/ArgumentAlignment: - 'lib/gitlab/ci/config/entry/root.rb' - 'lib/gitlab/ci/config/entry/rules/rule/changes.rb' - 'lib/gitlab/ci/config/extendable/entry.rb' - - 'lib/gitlab/ci/config/external/mapper/matcher.rb' - 'lib/gitlab/ci/lint.rb' - 'lib/gitlab/ci/status/stage/common.rb' - 'lib/gitlab/ci/trace/chunked_io.rb' @@ -2660,7 +2639,6 @@ Layout/ArgumentAlignment: - 'spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb' - 'spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb' - 'spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb' - - 'spec/lib/gitlab/database/tables_locker_spec.rb' - 'spec/lib/gitlab/database/tables_truncate_spec.rb' - 'spec/lib/gitlab/dependency_linker/parser/gemfile_spec.rb' - 'spec/lib/gitlab/diff/file_collection/compare_spec.rb' @@ -2768,7 +2746,6 @@ Layout/ArgumentAlignment: - 'spec/mailers/emails/merge_requests_spec.rb' - 'spec/mailers/emails/pipelines_spec.rb' - 'spec/mailers/notify_spec.rb' - - 'spec/migrations/20210708130419_reschedule_merge_request_diff_users_background_migration_spec.rb' - 'spec/migrations/20210918201050_remove_old_pending_jobs_for_recalculate_vulnerabilities_occurrences_uuid_spec.rb' - 'spec/migrations/20211110151350_schedule_drop_invalid_security_findings_spec.rb' - 'spec/migrations/20211116111644_schedule_remove_occurrence_pipelines_and_duplicate_vulnerabilities_findings_spec.rb' @@ -2795,9 +2772,6 @@ Layout/ArgumentAlignment: - 'spec/migrations/cleanup_backfill_integrations_enable_ssl_verification_spec.rb' - 'spec/migrations/cleanup_vulnerability_state_transitions_with_same_from_state_to_state_spec.rb' - 'spec/migrations/delete_migrate_shared_vulnerability_scanners_spec.rb' - - 'spec/migrations/migrate_protected_attribute_to_pending_builds_spec.rb' - - 'spec/migrations/re_schedule_latest_pipeline_id_population_with_all_security_related_artifact_types_spec.rb' - - 'spec/migrations/reschedule_delete_orphaned_deployments_spec.rb' - 'spec/migrations/schedule_backfill_draft_status_on_merge_requests_corrected_regex_spec.rb' - 'spec/migrations/schedule_fixing_security_scan_statuses_spec.rb' - 'spec/migrations/schedule_purging_stale_security_scans_spec.rb' @@ -3168,7 +3142,6 @@ Layout/ArgumentAlignment: - 'spec/services/task_list_toggle_service_spec.rb' - 'spec/services/todo_service_spec.rb' - 'spec/services/users/activity_service_spec.rb' - - 'spec/services/users/assigned_issues_count_service_spec.rb' - 'spec/services/users/destroy_service_spec.rb' - 'spec/services/users/migrate_records_to_ghost_user_in_batches_service_spec.rb' - 'spec/services/users/migrate_records_to_ghost_user_service_spec.rb' @@ -3181,7 +3154,6 @@ Layout/ArgumentAlignment: - 'spec/support/helpers/api_internal_base_helpers.rb' - 'spec/support/helpers/board_helpers.rb' - 'spec/support/helpers/ci/source_pipeline_helpers.rb' - - 'spec/support/helpers/ci/template_helpers.rb' - 'spec/support/helpers/feature_flag_helpers.rb' - 'spec/support/helpers/graphql_helpers.rb' - 'spec/support/helpers/stub_object_storage.rb' diff --git a/.rubocop_todo/rspec/factory_bot/avoid_create.yml b/.rubocop_todo/rspec/factory_bot/avoid_create.yml index 0a4ea883cbe..6f4f9ed61b8 100644 --- a/.rubocop_todo/rspec/factory_bot/avoid_create.yml +++ b/.rubocop_todo/rspec/factory_bot/avoid_create.yml @@ -235,7 +235,6 @@ RSpec/FactoryBot/AvoidCreate: - 'ee/spec/views/projects/security/sast_configuration/show.html.haml_spec.rb' - 'ee/spec/views/projects/settings/subscriptions/_index.html.haml_spec.rb' - 'ee/spec/views/registrations/groups_projects/new.html.haml_spec.rb' - - 'ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb' - 'ee/spec/views/registrations/welcome/show.html.haml_spec.rb' - 'ee/spec/views/search/_category.html.haml_spec.rb' - 'ee/spec/views/shared/_clone_panel.html.haml_spec.rb' diff --git a/.rubocop_todo/rspec/missing_feature_category.yml b/.rubocop_todo/rspec/missing_feature_category.yml index 32a479a4ba5..91b2fb06070 100644 --- a/.rubocop_todo/rspec/missing_feature_category.yml +++ b/.rubocop_todo/rspec/missing_feature_category.yml @@ -2420,7 +2420,6 @@ RSpec/MissingFeatureCategory: - 'ee/spec/views/projects/settings/subscriptions/_index.html.haml_spec.rb' - 'ee/spec/views/registrations/company/new.html.haml_spec.rb' - 'ee/spec/views/registrations/groups_projects/new.html.haml_spec.rb' - - 'ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb' - 'ee/spec/views/registrations/welcome/show.html.haml_spec.rb' - 'ee/spec/views/shared/_clone_panel.html.haml_spec.rb' - 'ee/spec/views/shared/_kerberos_clone_button.html.haml_spec.rb' diff --git a/app/assets/javascripts/boards/components/board_content.vue b/app/assets/javascripts/boards/components/board_content.vue index 66fd2af5aa3..8a37719eae8 100644 --- a/app/assets/javascripts/boards/components/board_content.vue +++ b/app/assets/javascripts/boards/components/board_content.vue @@ -116,6 +116,8 @@ export default { value: this.boardListsToUse, delay: 100, delayOnTouchOnly: true, + filter: 'input', + preventOnFilter: false, }; return this.canDragColumns ? options : {}; diff --git a/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue b/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue index 2028af8b8f0..85fe3477d7c 100644 --- a/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue +++ b/app/assets/javascripts/pages/projects/forks/new/components/fork_form.vue @@ -16,7 +16,7 @@ import { createAlert } from '~/flash'; import axios from '~/lib/utils/axios_utils'; import csrf from '~/lib/utils/csrf'; import { redirectTo } from '~/lib/utils/url_utility'; -import { s__ } from '~/locale'; +import { s__, __ } from '~/locale'; import validation from '~/vue_shared/directives/validation'; import { VISIBILITY_LEVEL_PRIVATE_STRING, @@ -25,8 +25,24 @@ import { VISIBILITY_LEVELS_STRING_TO_INTEGER, VISIBILITY_LEVELS_INTEGER_TO_STRING, } from '~/visibility_level/constants'; +import { START_RULE, CONTAINS_RULE } from '~/projects/project_name_rules'; import ProjectNamespace from './project_namespace.vue'; +const feedbackMap = { + valueMissing: { + isInvalid: (el) => el.validity?.valueMissing, + message: __('Please fill out this field.'), + }, + nameStartPattern: { + isInvalid: (el) => el.validity?.patternMismatch && !START_RULE.reg.test(el.value), + message: START_RULE.msg, + }, + nameContainsPattern: { + isInvalid: (el) => el.validity?.patternMismatch && !CONTAINS_RULE.reg.test(el.value), + message: CONTAINS_RULE.msg, + }, +}; + const initFormField = ({ value, required = true, skipValidation = false }) => ({ value, required, @@ -48,7 +64,7 @@ export default { ProjectNamespace, }, directives: { - validation: validation(), + validation: validation(feedbackMap), }, inject: { newGroupPath: { @@ -109,6 +125,15 @@ export default { }; }, computed: { + projectNameDescription() { + if (this.form.fields.name.state === false) { + return null; + } + + return s__( + 'ProjectsNew|Must start with a lowercase or uppercase letter, digit, emoji, or underscore. Can also contain dots, pluses, dashes, or spaces.', + ); + }, projectVisibilityLevel() { return VISIBILITY_LEVELS_STRING_TO_INTEGER[this.projectVisibility]; }, @@ -248,6 +273,7 @@ export default { }, }, csrf, + projectNamePattern: `(${START_RULE.reg.source})|(${CONTAINS_RULE.reg.source})`, }; </script> @@ -257,8 +283,10 @@ export default { <gl-form-group :label="__('Project name')" + :description="projectNameDescription" label-for="fork-name" :invalid-feedback="form.fields.name.feedback" + data-testid="fork-name-form-group" > <gl-form-input id="fork-name" @@ -268,6 +296,7 @@ export default { data-testid="fork-name-input" :state="form.fields.name.state" required + :pattern="$options.projectNamePattern" /> </gl-form-group> diff --git a/app/assets/javascripts/projects/project_name_rules.js b/app/assets/javascripts/projects/project_name_rules.js index eeef1fb5afc..4f62aa29ce4 100644 --- a/app/assets/javascripts/projects/project_name_rules.js +++ b/app/assets/javascripts/projects/project_name_rules.js @@ -1,28 +1,29 @@ import { __ } from '~/locale'; -const rulesReg = [ - { - reg: /^[a-zA-Z0-9\u{00A9}-\u{1f9ff}_]/u, - msg: __("Name must start with a letter, digit, emoji, or '_'"), - }, - { - reg: /^[a-zA-Z0-9\p{Pd}\u{002B}\u{00A9}-\u{1f9ff}_. ]+$/u, - msg: __("Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces"), - }, -]; +export const START_RULE = { + reg: /^[a-zA-Z0-9\u{00A9}-\u{1f9ff}_]/u, + msg: __('Name must start with a letter, digit, emoji, or underscore.'), +}; + +export const CONTAINS_RULE = { + reg: /^[a-zA-Z0-9\p{Pd}\u{002B}\u{00A9}-\u{1f9ff}_. ]+$/u, + msg: __( + 'Name can contain only lowercase or uppercase letters, digits, emojis, spaces, dots, underscores, dashes, or pluses.', + ), +}; + +const rulesReg = [START_RULE, CONTAINS_RULE]; /** * * @param {string} text * @returns {string} msg */ -function checkRules(text) { +export const checkRules = (text) => { for (const item of rulesReg) { if (!item.reg.test(text)) { return item.msg; } } return ''; -} - -export { checkRules }; +}; diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index d71e80dffcf..99ea02aaa4f 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -90,13 +90,16 @@ const validateGroupNamespaceDropdown = (e) => { const checkProjectName = (projectNameInput) => { const msg = checkRules(projectNameInput.value); - const projectNameError = document.querySelector('#project_name_error'); + const projectNameError = document.querySelector('#js-project-name-error'); + const projectNameDescription = document.getElementById('js-project-name-description'); if (!projectNameError) return; if (msg) { projectNameError.innerText = msg; - projectNameError.classList.remove('hidden'); + projectNameError.classList.remove('gl-display-none'); + projectNameDescription.classList.add('gl-display-none'); } else { - projectNameError.classList.add('hidden'); + projectNameError.classList.add('gl-display-none'); + projectNameDescription.classList.remove('gl-display-none'); } }; diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss index 195a66bf9e5..3ac63b8f608 100644 --- a/app/assets/stylesheets/framework/images.scss +++ b/app/assets/stylesheets/framework/images.scss @@ -1,4 +1,5 @@ -.appearance-logo-preview { +.appearance-logo-preview, +.appearance-pwa-icon-preview { max-width: 400px; margin-bottom: 20px; } diff --git a/app/controllers/admin/application_settings/appearances_controller.rb b/app/controllers/admin/application_settings/appearances_controller.rb index 7950c207f69..719e8e4a913 100644 --- a/app/controllers/admin/application_settings/appearances_controller.rb +++ b/app/controllers/admin/application_settings/appearances_controller.rb @@ -46,6 +46,15 @@ class Admin::ApplicationSettings::AppearancesController < Admin::ApplicationCont redirect_to admin_application_settings_appearances_path, notice: _('Header logo was successfully removed.') end + def pwa_icon + @appearance.remove_pwa_icon! + + @appearance.save + + redirect_to admin_application_settings_appearances_path, + notice: _('Progressive Web App (PWA) icon was successfully removed.') + end + def favicon @appearance.remove_favicon! @appearance.save @@ -76,6 +85,8 @@ class Admin::ApplicationSettings::AppearancesController < Admin::ApplicationCont logo_cache header_logo header_logo_cache + pwa_icon + pwa_icon_cache favicon favicon_cache new_project_guidelines diff --git a/app/models/appearance.rb b/app/models/appearance.rb index 833f2335774..b926c6abedc 100644 --- a/app/models/appearance.rb +++ b/app/models/appearance.rb @@ -27,15 +27,21 @@ class Appearance < ApplicationRecord cache_markdown_field :footer_message, pipeline: :broadcast_message validates :pwa_name, - length: { maximum: 255, message: N_("is too long (maximum is %{count} characters)") }, + length: { maximum: 255, too_long: ->(object, data) { + N_("is too long (maximum is %{count} characters)") + } }, allow_blank: true validates :pwa_short_name, - length: { maximum: 255, message: N_("is too long (maximum is %{count} characters)") }, + length: { maximum: 255, too_long: ->(object, data) { + N_("is too long (maximum is %{count} characters)") + } }, allow_blank: true validates :pwa_description, - length: { maximum: 2048, message: N_("is too long (maximum is %{count} characters)") }, + length: { maximum: 2048, too_long: ->(object, data) { + N_("is too long (maximum is %{count} characters)") + } }, allow_blank: true validates :logo, file_size: { maximum: 1.megabyte } diff --git a/app/views/admin/application_settings/appearances/_form.html.haml b/app/views/admin/application_settings/appearances/_form.html.haml index c20a86b9f9c..6c6334905ca 100644 --- a/app/views/admin/application_settings/appearances/_form.html.haml +++ b/app/views/admin/application_settings/appearances/_form.html.haml @@ -10,7 +10,7 @@ .col-lg-8 .form-group - = f.label :header_logo, _('Header logo'), class: 'col-form-label label-bold pt-0' + = f.label :header_logo, _('Header logo'), class: 'col-form-label gl-pt-0' %p - if @appearance.header_logo? = image_tag @appearance.header_logo_path, class: 'appearance-light-logo-preview' @@ -29,7 +29,7 @@ .col-lg-8 .form-group - = f.label :favicon, _('Favicon'), class: 'col-form-label label-bold pt-0' + = f.label :favicon, _('Favicon'), class: 'col-form-label gl-pt-0' %p - if @appearance.favicon? = image_tag @appearance.favicon_path, class: 'appearance-light-logo-preview' @@ -53,15 +53,15 @@ .col-lg-8 .form-group - = f.label :title, class: 'col-form-label label-bold' + = f.label :title, class: 'col-form-label' = f.text_field :title, class: "form-control gl-form-input" .form-group - = f.label :description, class: 'col-form-label label-bold' + = f.label :description, class: 'col-form-label' = f.text_area :description, class: "form-control gl-form-input", rows: 10 .form-text.text-muted = parsed_with_gfm .form-group - = f.label :logo, class: 'col-form-label label-bold pt-0' + = f.label :logo, class: 'col-form-label gl-pt-0' %p - if @appearance.logo? = image_tag @appearance.logo_path, class: 'appearance-logo-preview' @@ -77,11 +77,42 @@ %hr .row .col-lg-4.profile-settings-sidebar + %h4.gl-mt-0= _('Progressive Web App (PWA)') + + .col-lg-8 + .form-group + = f.label _("Name"), class: 'col-form-label' + = f.text_field :pwa_name, class: "form-control gl-form-input" + .form-group + = f.label _("Short name"), class: 'col-form-label' + = f.text_field :pwa_short_name, class: "form-control gl-form-input" + .form-group + = f.label _("Description"), class: 'col-form-label' + = f.text_area :pwa_description, class: "form-control gl-form-input", rows: 10 + .form-text.text-muted + = parsed_with_gfm + .form-group + = f.label :pwa_icon, class: 'col-form-label gl-pt-0' + %p + - if @appearance.pwa_icon? + = image_tag @appearance.pwa_icon_path, class: 'appearance-pwa-icon-preview' + - if @appearance.persisted? + %br + = link_to _('Remove icon'), pwa_icon_admin_application_settings_appearances_path, data: { confirm: _("Icon will be removed. Are you sure?"), confirm_btn_variant: "danger" }, aria: { label: _('Remove icon') }, method: :delete, class: "btn gl-button btn-danger btn-danger-secondary btn-sm remove-logo" + %hr + = f.hidden_field :pwa_icon_cache + = f.file_field :pwa_icon, class: "", accept: 'image/*' + .form-text.text-muted + = _('Maximum file size is 1MB.') + + %hr + .row + .col-lg-4.profile-settings-sidebar %h4.gl-mt-0= _('New project pages') .col-lg-8 .form-group - = f.label :new_project_guidelines, class: 'col-form-label label-bold' + = f.label :new_project_guidelines, class: 'col-form-label' %p = f.text_area :new_project_guidelines, class: "form-control gl-form-input", rows: 10 .form-text.text-muted @@ -94,7 +125,7 @@ .col-lg-8 .form-group - = f.label :profile_image_guidelines, class: 'col-form-label label-bold' + = f.label :profile_image_guidelines, class: 'col-form-label' %p = f.text_area :profile_image_guidelines, class: "form-control gl-form-input", rows: 10 .form-text.text-muted diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml index ec83782985b..53a1abdff33 100644 --- a/app/views/projects/_new_project_fields.html.haml +++ b/app/views/projects/_new_project_fields.html.haml @@ -6,11 +6,13 @@ .row{ id: project_name_id } = f.hidden_field :ci_cd_only, value: ci_cd_only - .form-group.project-name.col-sm-12 + .form-group.gl-form-group.project-name.col-sm-12 = f.label :name, class: 'label-bold' do %span= _("Project name") = f.text_field :name, placeholder: "My awesome project", class: "form-control gl-form-input input-lg", data: { qa_selector: 'project_name', track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_name", track_value: "" }, required: true, aria: { required: true } - #project_name_error.gl-field-error.hidden + %small#js-project-name-description.form-text.text-gl-muted + = s_("ProjectsNew|Must start with a lowercase or uppercase letter, digit, emoji, or underscore. Can also contain dots, pluses, dashes, or spaces.") + #js-project-name-error.gl-field-error.gl-mt-2.gl-display-none .form-group.project-path.col-sm-6.gl-pr-0 = f.label :namespace_id, class: 'label-bold' do %span= _('Project URL') diff --git a/config/feature_flags/development/improved_spread_parallel_import.yml b/config/feature_flags/development/improved_spread_parallel_import.yml index 803af69344f..a1d7caf12b0 100644 --- a/config/feature_flags/development/improved_spread_parallel_import.yml +++ b/config/feature_flags/development/improved_spread_parallel_import.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/388665 milestone: '15.9' type: development group: group::import -default_enabled: false +default_enabled: true diff --git a/config/locales/en.yml b/config/locales/en.yml index d5c7eecb068..796ca56c604 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -5,6 +5,11 @@ en: hello: "Hello world" activerecord: attributes: + appearance: + pwa_name: "PWA name" + pwa_short_name: "PWA short name" + pwa_description: "PWA description" + pwa_icon: "Icon" incident_management/timeline_event: note: 'Timeline text' issue_link: diff --git a/config/routes.rb b/config/routes.rb index eec3bb944fb..af1ecc687e0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -61,11 +61,7 @@ InitializerConnections.with_disabled_database_connections do # Sign up scope path: '/users/sign_up', module: :registrations, as: :users_sign_up do - resource :welcome, only: [:show, :update], controller: 'welcome' do - Gitlab.ee do - get :continuous_onboarding_getting_started, on: :collection - end - end + resource :welcome, only: [:show, :update], controller: 'welcome' Gitlab.ee do resource :company, only: [:new, :create], controller: 'company' diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 6d1c3c661c0..9181c1c94cf 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -155,6 +155,7 @@ namespace :admin do member do get :preview_sign_in delete :logo + delete :pwa_icon delete :header_logos delete :favicon end diff --git a/db/post_migrate/20230126091522_add_unique_index_to_ci_build_pending_state.rb b/db/post_migrate/20230126091522_add_unique_index_to_ci_build_pending_state.rb new file mode 100644 index 00000000000..484b64feb97 --- /dev/null +++ b/db/post_migrate/20230126091522_add_unique_index_to_ci_build_pending_state.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +class AddUniqueIndexToCiBuildPendingState < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + OLD_INDEX_NAME = :index_ci_build_pending_states_on_partition_id_build_id + NEW_INDEX_NAME = :unique_index_ci_build_pending_states_on_partition_id_build_id + TABLE_NAME = :ci_build_pending_states + COLUMNS = [:partition_id, :build_id] + + def up + add_concurrent_index(TABLE_NAME, COLUMNS, unique: true, name: NEW_INDEX_NAME) + + remove_concurrent_index_by_name(TABLE_NAME, OLD_INDEX_NAME) + end + + def down + add_concurrent_index(TABLE_NAME, COLUMNS, name: OLD_INDEX_NAME) + + remove_concurrent_index_by_name(TABLE_NAME, NEW_INDEX_NAME) + end +end diff --git a/db/schema_migrations/20230126091522 b/db/schema_migrations/20230126091522 new file mode 100644 index 00000000000..b179f736257 --- /dev/null +++ b/db/schema_migrations/20230126091522 @@ -0,0 +1 @@ +48276b76dbedc046f7270e7204558ab045e48a6d7da9e1c0a58a76b06e51bfa5
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index ad2d102b5ee..69bc358b06e 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -29081,8 +29081,6 @@ CREATE INDEX index_ci_build_needs_on_partition_id_build_id ON ci_build_needs USI CREATE UNIQUE INDEX index_ci_build_pending_states_on_build_id ON ci_build_pending_states USING btree (build_id); -CREATE INDEX index_ci_build_pending_states_on_partition_id_build_id ON ci_build_pending_states USING btree (partition_id, build_id); - CREATE UNIQUE INDEX index_ci_build_report_results_on_partition_id_build_id ON ci_build_report_results USING btree (partition_id, build_id); CREATE INDEX index_ci_build_report_results_on_project_id ON ci_build_report_results USING btree (project_id); @@ -32207,6 +32205,8 @@ CREATE UNIQUE INDEX uniq_pkgs_debian_project_distributions_project_id_and_suite CREATE UNIQUE INDEX unique_ci_builds_token_encrypted_and_partition_id ON ci_builds USING btree (token_encrypted, partition_id) WHERE (token_encrypted IS NOT NULL); +CREATE UNIQUE INDEX unique_index_ci_build_pending_states_on_partition_id_build_id ON ci_build_pending_states USING btree (partition_id, build_id); + CREATE UNIQUE INDEX unique_merge_request_metrics_by_merge_request_id ON merge_request_metrics USING btree (merge_request_id); CREATE UNIQUE INDEX unique_projects_on_name_namespace_id ON projects USING btree (name, namespace_id); diff --git a/doc/api/members.md b/doc/api/members.md index 4032ab1d651..950289effd2 100644 --- a/doc/api/members.md +++ b/doc/api/members.md @@ -574,6 +574,7 @@ PUT /projects/:id/members/:user_id | `user_id` | integer | yes | The user ID of the member | | `access_level` | integer | yes | A valid access level | | `expires_at` | string | no | A date string in the format `YEAR-MONTH-DAY` | +| `member_role_id` | integer | no | The ID of a member role **(ULTIMATE)** | ```shell curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/:id/members/:user_id?access_level=40" diff --git a/doc/ci/environments/index.md b/doc/ci/environments/index.md index 887b51f2832..bee923c9648 100644 --- a/doc/ci/environments/index.md +++ b/doc/ci/environments/index.md @@ -618,6 +618,16 @@ Because `stop_review_app` is set to `auto_stop_in: 1 week`, if a merge request is inactive for more than a week, GitLab automatically triggers the `stop_review_app` job to stop the environment. +#### Stop an environment without running the `on_stop` action + +There may be times when you want to stop an environment without running the defined +[`on_stop`](../yaml/index.md#environmenton_stop) action. For example, you want to delete many +environments without using CI/CD minutes. + +To stop an environment without running the defined `on_stop` action, execute the +[Stop an environment API](../../api/environments.md#stop-an-environment) with the parameter +`force=true`. + #### Stop an environment through the UI NOTE: diff --git a/doc/user/project/index.md b/doc/user/project/index.md index d30136e57e4..decf3b071e7 100644 --- a/doc/user/project/index.md +++ b/doc/user/project/index.md @@ -16,8 +16,7 @@ To create a blank project: 1. On the right of the page, select **New project**. 1. Select **Create blank project**. 1. Enter the project details: - - In the **Project name** field, enter the name of your project. You cannot use special characters at - the start or end of a project name. + - In the **Project name** field, enter the name of your project. The name must start with a lowercase or uppercase letter (`a-zA-Z`), digit (`0-9`), emoji, or underscore (`_`). It can also contain dots (`.`), pluses (`+`), dashes (`-`), or spaces. - In the **Project slug** field, enter the path to your project. The GitLab instance uses the slug as the URL path to the project. To change the slug, first enter the project name, then change the slug. @@ -51,8 +50,7 @@ To create a project from a built-in template: - To view a preview of the template, select **Preview**. - To use a template for the project, select **Use template**. 1. Enter the project details: - - In the **Project name** field, enter the name of your project. You cannot use special characters at - the start or end of a project name. + - In the **Project name** field, enter the name of your project. The name must start with a lowercase or uppercase letter (`a-zA-Z`), digit (`0-9`), emoji, or underscore (`_`). It can also contain dots (`.`), pluses (`+`), dashes (`-`), or spaces. - In the **Project slug** field, enter the path to your project. The GitLab instance uses the slug as the URL path to the project. To change the slug, first enter the project name, then change the slug. @@ -83,8 +81,7 @@ Custom project templates are available at: - To view a preview of the template, select **Preview**. - To use a template for the project, select **Use template**. 1. Enter the project details: - - In the **Project name** field, enter the name of your project. You cannot use special characters at - the start or end of a project name. + - In the **Project name** field, enter the name of your project. The name must start with a lowercase or uppercase letter (`a-zA-Z`), digit (`0-9`), emoji, or underscore (`_`). It can also contain dots (`.`), pluses (`+`), dashes (`-`), or spaces. - In the **Project slug** field, enter the path to your project. The GitLab instance uses the slug as the URL path to the project. To change the slug, first enter the project name, then change the slug. @@ -110,8 +107,7 @@ To create a project from the HIPAA Audit Protocol template: - To view a preview of the template, select **Preview**. - To use the template for the project, select **Use template**. 1. Enter the project details: - - In the **Project name** field, enter the name of your project. You cannot use special characters at - the start or end of a project name. + - In the **Project name** field, enter the name of your project. The name must start with a lowercase or uppercase letter (`a-zA-Z`), digit (`0-9`), emoji, or underscore (`_`). It can also contain dots (`.`), pluses (`+`), dashes (`-`), or spaces. - In the **Project slug** field, enter the path to your project. The GitLab instance uses the slug as the URL path to the project. To change the slug, first enter the project name, then change the slug. diff --git a/lib/api/helpers/members_helpers.rb b/lib/api/helpers/members_helpers.rb index a406a62344a..4b34a2bbe79 100644 --- a/lib/api/helpers/members_helpers.rb +++ b/lib/api/helpers/members_helpers.rb @@ -11,6 +11,9 @@ module API params :optional_state_filter_ee do end + params :optional_put_params_ee do + end + def find_source(source_type, id) public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend end diff --git a/lib/api/members.rb b/lib/api/members.rb index 32c5227a939..1e640a6542a 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -139,6 +139,7 @@ module API requires :user_id, type: Integer, desc: 'The user ID of the new member' requires :access_level, type: Integer, desc: 'A valid access level' optional :expires_at, type: DateTime, desc: 'Date string in the format YEAR-MONTH-DAY' + use :optional_put_params_ee end # rubocop: disable CodeReuse/ActiveRecord put ":id/members/:user_id", feature_category: feature_category do diff --git a/lib/gitlab/cache/helpers.rb b/lib/gitlab/cache/helpers.rb index 024fa48c066..0fc0b1504af 100644 --- a/lib/gitlab/cache/helpers.rb +++ b/lib/gitlab/cache/helpers.rb @@ -45,7 +45,14 @@ module Gitlab def contextual_cache_key(presenter, object, context) return object.cache_key if context.nil? - [presenter.class.name, object.cache_key, context.call(object)].flatten.join(":") + [presenter_class_name(presenter), object.cache_key, context.call(object)].flatten.join(":") + end + + def presenter_class_name(presenter) + return presenter.class.name if presenter.is_a?(BaseSerializer) + return presenter.name if presenter.is_a?(Class) && presenter < Grape::Entity + + raise ArgumentError, "presenter #{presenter} is not supported" end # Used for fetching or rendering a single object diff --git a/lib/gitlab/database/migrations/base_background_runner.rb b/lib/gitlab/database/migrations/base_background_runner.rb index 8975c04e33a..840add8783d 100644 --- a/lib/gitlab/database/migrations/base_background_runner.rb +++ b/lib/gitlab/database/migrations/base_background_runner.rb @@ -38,13 +38,15 @@ module Gitlab def run_jobs_for_migration(migration_name:, jobs:, run_until:) per_background_migration_result_dir = File.join(@result_dir, migration_name) - instrumentation = Instrumentation.new(result_dir: per_background_migration_result_dir) + instrumentation = Instrumentation.new(result_dir: per_background_migration_result_dir, + observer_classes: observers) + batch_names = (1..).each.lazy.map { |i| "batch_#{i}" } jobs.each do |j| break if run_until <= Time.current - meta = migration_meta(j) + meta = { job_meta: job_meta(j) } instrumentation.observe(version: nil, name: batch_names.next, @@ -55,9 +57,13 @@ module Gitlab end end - def migration_meta(_job) + def job_meta(_job) {} end + + def observers + ::Gitlab::Database::Migrations::Observers.all_observers + end end end end diff --git a/lib/gitlab/database/migrations/observation.rb b/lib/gitlab/database/migrations/observation.rb index 80388c4dbbb..cd048beac96 100644 --- a/lib/gitlab/database/migrations/observation.rb +++ b/lib/gitlab/database/migrations/observation.rb @@ -4,16 +4,12 @@ module Gitlab module Database module Migrations - Observation = Struct.new( - :version, - :name, - :walltime, - :success, - :total_database_size_change, - :meta, - :query_statistics, - keyword_init: true - ) + Observation = Struct.new(:version, :name, :walltime, :success, :total_database_size_change, + :meta, :query_statistics, keyword_init: true) do + def to_json(...) + as_json.except('meta').to_json(...) + end + end end end end diff --git a/lib/gitlab/database/migrations/observers/batch_details.rb b/lib/gitlab/database/migrations/observers/batch_details.rb new file mode 100644 index 00000000000..0f8cdcf3cd6 --- /dev/null +++ b/lib/gitlab/database/migrations/observers/batch_details.rb @@ -0,0 +1,53 @@ +# frozen_string_literal: true + +module Gitlab + module Database + module Migrations + module Observers + class BatchDetails < MigrationObserver + FILE_NAME = 'batch-details.json' + + def before + @started_at = get_time + end + + def after + @finished_at = get_time + end + + def record + File.open(path, 'wb') { |file| file.write(file_contents.to_json) } + end + + private + + attr_reader :started_at, :finished_at + + def file_contents + { time_spent: time_spent }.merge(job_meta) + end + + def get_time + Process.clock_gettime(Process::CLOCK_MONOTONIC) + end + + def time_spent + @time_spent ||= (finished_at - started_at).round(2) + end + + def path + File.join(output_dir, FILE_NAME) + end + + def job_meta + meta = observation.meta + + return {} unless meta + + meta[:job_meta].to_h + end + end + end + end + end +end diff --git a/lib/gitlab/database/migrations/test_batched_background_runner.rb b/lib/gitlab/database/migrations/test_batched_background_runner.rb index c123d01f327..3063430091d 100644 --- a/lib/gitlab/database/migrations/test_batched_background_runner.rb +++ b/lib/gitlab/database/migrations/test_batched_background_runner.rb @@ -6,6 +6,8 @@ module Gitlab class TestBatchedBackgroundRunner < BaseBackgroundRunner include Gitlab::Database::DynamicModelHelpers + MIGRATION_DETAILS_FILE_NAME = 'details.json' + def initialize(result_dir:, connection:, from_id:) super(result_dir: result_dir, connection: connection) @connection = connection @@ -64,7 +66,11 @@ module Gitlab end end - [migration.job_class_name, jobs_to_sample] + job_class_name = migration.job_class_name + + export_migration_details(job_class_name, migration.slice(:interval, :total_tuple_count, :max_batch_size)) + + [job_class_name, jobs_to_sample] end end end @@ -112,11 +118,25 @@ module Gitlab Gitlab::Database::SharedModel.using_connection(connection, &block) end - def migration_meta(job) + def job_meta(job) set_shared_model_connection do - job.batched_migration.slice(:max_batch_size, :total_tuple_count, :interval) + job.slice(:min_value, :max_value, :batch_size, :sub_batch_size, :pause_ms) end end + + def export_migration_details(migration_name, attributes) + directory = result_dir.join(migration_name) + + FileUtils.mkdir_p(directory) unless Dir.exist?(directory) + + File.write(directory.join(MIGRATION_DETAILS_FILE_NAME), attributes.to_json) + end + + def observers + ::Gitlab::Database::Migrations::Observers.all_observers + [ + ::Gitlab::Database::Migrations::Observers::BatchDetails + ] + end end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 9b002c7bdcc..78d10e5b54d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -20969,6 +20969,9 @@ msgstr "" msgid "IP subnet restriction only allowed for top-level groups" msgstr "" +msgid "Icon will be removed. Are you sure?" +msgstr "" + msgid "Id" msgstr "" @@ -25969,6 +25972,9 @@ msgstr "" msgid "Maximum file size is 1 MB. Pages are optimized for a 128x128 px logo." msgstr "" +msgid "Maximum file size is 1MB." +msgstr "" + msgid "Maximum file size is 1MB. Pages are optimized for a 24px tall header logo" msgstr "" @@ -27573,7 +27579,7 @@ msgstr "" msgid "Name" msgstr "" -msgid "Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces" +msgid "Name can contain only lowercase or uppercase letters, digits, emojis, spaces, dots, underscores, dashes, or pluses." msgstr "" msgid "Name can't be blank" @@ -27585,7 +27591,7 @@ msgstr "" msgid "Name is already taken." msgstr "" -msgid "Name must start with a letter, digit, emoji, or '_'" +msgid "Name must start with a letter, digit, emoji, or underscore." msgstr "" msgid "Name new label" @@ -32891,6 +32897,12 @@ msgstr "" msgid "Progress tracking" msgstr "" +msgid "Progressive Web App (PWA)" +msgstr "" + +msgid "Progressive Web App (PWA) icon was successfully removed." +msgstr "" + msgid "Project" msgstr "" @@ -34022,6 +34034,9 @@ msgstr "" msgid "ProjectsNew|Migrate your data from an external source like GitHub, Bitbucket, or another instance of GitLab." msgstr "" +msgid "ProjectsNew|Must start with a lowercase or uppercase letter, digit, emoji, or underscore. Can also contain dots, pluses, dashes, or spaces." +msgstr "" + msgid "ProjectsNew|No import options available" msgstr "" @@ -35436,6 +35451,9 @@ msgstr "" msgid "Remove header logo" msgstr "" +msgid "Remove icon" +msgstr "" + msgid "Remove iteration" msgstr "" @@ -39514,6 +39532,9 @@ msgstr "" msgid "Shimo|You've enabled the Shimo Workspace integration. You can view your wiki directly in Shimo." msgstr "" +msgid "Short name" +msgstr "" + msgid "Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %{boldStart}will%{boldEnd} lose access to your account." msgstr "" diff --git a/spec/factories/member_roles.rb b/spec/factories/member_roles.rb index 08df45a85f8..503438d2521 100644 --- a/spec/factories/member_roles.rb +++ b/spec/factories/member_roles.rb @@ -5,6 +5,7 @@ FactoryBot.define do namespace { association(:group) } base_access_level { Gitlab::Access::DEVELOPER } - trait(:guest) { base_access_level { GroupMember::GUEST } } + trait(:developer) { base_access_level { Gitlab::Access::DEVELOPER } } + trait(:guest) { base_access_level { Gitlab::Access::GUEST } } end end diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb index 5fbe7039c1d..252d9ac5bac 100644 --- a/spec/features/admin/admin_appearance_spec.rb +++ b/spec/features/admin/admin_appearance_spec.rb @@ -19,6 +19,9 @@ RSpec.describe 'Admin Appearance', feature_category: :not_owned do fill_in 'appearance_title', with: 'MyCompany' fill_in 'appearance_description', with: 'dev server' + fill_in 'appearance_pwa_name', with: 'GitLab PWA' + fill_in 'appearance_pwa_short_name', with: 'GitLab' + fill_in 'appearance_pwa_description', with: 'GitLab as PWA' fill_in 'appearance_new_project_guidelines', with: 'Custom project guidelines' fill_in 'appearance_profile_image_guidelines', with: 'Custom profile image guidelines' click_button 'Update appearance settings' @@ -28,6 +31,9 @@ RSpec.describe 'Admin Appearance', feature_category: :not_owned do expect(page).to have_field('appearance_title', with: 'MyCompany') expect(page).to have_field('appearance_description', with: 'dev server') + expect(page).to have_field('appearance_pwa_name', with: 'GitLab PWA') + expect(page).to have_field('appearance_pwa_short_name', with: 'GitLab') + expect(page).to have_field('appearance_pwa_description', with: 'GitLab as PWA') expect(page).to have_field('appearance_new_project_guidelines', with: 'Custom project guidelines') expect(page).to have_field('appearance_profile_image_guidelines', with: 'Custom profile image guidelines') expect(page).to have_content 'Last edit' @@ -135,6 +141,19 @@ RSpec.describe 'Admin Appearance', feature_category: :not_owned do expect(page).not_to have_css(logo_selector) end + it 'appearance pwa icon' do + sign_in(admin) + gitlab_enable_admin_mode_sign_in(admin) + visit admin_application_settings_appearances_path + + attach_file(:appearance_pwa_icon, logo_fixture) + click_button 'Update appearance settings' + expect(page).to have_css(pwa_icon_selector) + + click_link 'Remove icon' + expect(page).not_to have_css(pwa_icon_selector) + end + it 'header logos' do sign_in(admin) gitlab_enable_admin_mode_sign_in(admin) @@ -183,6 +202,10 @@ RSpec.describe 'Admin Appearance', feature_category: :not_owned do '//img[data-src^="/uploads/-/system/appearance/logo"]' end + def pwa_icon_selector + '//img[data-src^="/uploads/-/system/appearance/pwa_icon"]' + end + def header_logo_selector '//img[data-src^="/uploads/-/system/appearance/header_logo"]' end diff --git a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js index aee56247209..f0593a854b2 100644 --- a/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js +++ b/spec/frontend/pages/projects/forks/new/components/fork_form_spec.js @@ -12,6 +12,7 @@ import ForkForm from '~/pages/projects/forks/new/components/fork_form.vue'; import createMockApollo from 'helpers/mock_apollo_helper'; import searchQuery from '~/pages/projects/forks/new/queries/search_forkable_namespaces.query.graphql'; import ProjectNamespace from '~/pages/projects/forks/new/components/project_namespace.vue'; +import { START_RULE, CONTAINS_RULE } from '~/projects/project_name_rules'; jest.mock('~/flash'); jest.mock('~/lib/utils/csrf', () => ({ token: 'mock-csrf-token' })); @@ -475,6 +476,43 @@ describe('ForkForm component', () => { expect(axios.post).not.toHaveBeenCalled(); }); + + describe('project name', () => { + it.each` + value | expectedErrorMessage + ${'?'} | ${START_RULE.msg} + ${'*'} | ${START_RULE.msg} + ${'a?'} | ${CONTAINS_RULE.msg} + ${'a*'} | ${CONTAINS_RULE.msg} + `( + 'shows "$expectedErrorMessage" error when value is $value', + async ({ value, expectedErrorMessage }) => { + createFullComponent(); + + findForkNameInput().vm.$emit('input', value); + await nextTick(); + await submitForm(); + + const formGroup = wrapper.findComponent('[data-testid="fork-name-form-group"]'); + + expect(formGroup.vm.$attrs['invalid-feedback']).toBe(expectedErrorMessage); + expect(formGroup.vm.$attrs.description).toBe(null); + }, + ); + + it.each(['a', '9', 'aa', '99'])('does not show error when value is %s', async (value) => { + createFullComponent(); + + findForkNameInput().vm.$emit('input', value); + await nextTick(); + await submitForm(); + + const formGroup = wrapper.findComponent('[data-testid="fork-name-form-group"]'); + + expect(formGroup.vm.$attrs['invalid-feedback']).toBe(''); + expect(formGroup.vm.$attrs.description).not.toBe(null); + }); + }); }); describe('with valid form', () => { diff --git a/spec/frontend/projects/project_new_spec.js b/spec/frontend/projects/project_new_spec.js index d69bfc4ec92..8a1e9904a3f 100644 --- a/spec/frontend/projects/project_new_spec.js +++ b/spec/frontend/projects/project_new_spec.js @@ -9,6 +9,7 @@ describe('New Project', () => { let $projectPath; let $projectName; let $projectNameError; + let $projectNameDescription; const mockKeyup = (el) => el.dispatchEvent(new KeyboardEvent('keyup')); const mockChange = (el) => el.dispatchEvent(new Event('change')); @@ -31,7 +32,8 @@ describe('New Project', () => { </div> </div> <input id="project_name" /> - <div class="gl-field-error hidden" id="project_name_error" /> + <small id="js-project-name-description" /> + <div class="gl-field-error gl-display-none" id="js-project-name-error" /> <input id="project_path" /> </div> <div class="js-user-readme-repo"></div> @@ -44,7 +46,8 @@ describe('New Project', () => { $projectImportUrl = document.querySelector('#project_import_url'); $projectPath = document.querySelector('#project_path'); $projectName = document.querySelector('#project_name'); - $projectNameError = document.querySelector('#project_name_error'); + $projectNameError = document.querySelector('#js-project-name-error'); + $projectNameDescription = document.querySelector('#js-project-name-description'); }); afterEach(() => { @@ -98,7 +101,7 @@ describe('New Project', () => { }); it('no error message by default', () => { - expect($projectNameError.classList.contains('hidden')).toBe(true); + expect($projectNameError.classList.contains('gl-display-none')).toBe(true); }); it('show error message if name is validate', () => { @@ -106,15 +109,16 @@ describe('New Project', () => { triggerEvent($projectName, 'change'); expect($projectNameError.innerText).toBe( - "Name must start with a letter, digit, emoji, or '_'", + 'Name must start with a letter, digit, emoji, or underscore.', ); - expect($projectNameError.classList.contains('hidden')).toBe(false); + expect($projectNameError.classList.contains('gl-display-none')).toBe(false); + expect($projectNameDescription.classList.contains('gl-display-none')).toBe(true); }); }); describe('project name rule', () => { describe("Name must start with a letter, digit, emoji, or '_'", () => { - const errormsg = "Name must start with a letter, digit, emoji, or '_'"; + const errormsg = 'Name must start with a letter, digit, emoji, or underscore.'; it("'.foo' should error", () => { const text = '.foo'; expect(checkRules(text)).toBe(errormsg); @@ -127,7 +131,7 @@ describe('New Project', () => { describe("Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces", () => { const errormsg = - "Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces"; + 'Name can contain only lowercase or uppercase letters, digits, emojis, spaces, dots, underscores, dashes, or pluses.'; it("'foo(#^.^#)foo' should error", () => { const text = 'foo(#^.^#)foo'; expect(checkRules(text)).toBe(errormsg); diff --git a/spec/lib/api/helpers/caching_spec.rb b/spec/lib/api/helpers/caching_spec.rb index 828af7b5f91..03025823357 100644 --- a/spec/lib/api/helpers/caching_spec.rb +++ b/spec/lib/api/helpers/caching_spec.rb @@ -47,12 +47,14 @@ RSpec.describe API::Helpers::Caching, :use_clean_rails_redis_caching do context 'single object' do let_it_be(:presentable) { create(:todo, project: project) } + let(:expected_cache_key_prefix) { 'API::Entities::Todo' } it_behaves_like 'object cache helper' end context 'collection of objects' do let_it_be(:presentable) { Array.new(5).map { create(:todo, project: project) } } + let(:expected_cache_key_prefix) { 'API::Entities::Todo' } it_behaves_like 'collection cache helper' end diff --git a/spec/lib/gitlab/cache/helpers_spec.rb b/spec/lib/gitlab/cache/helpers_spec.rb index 39d37e979b4..06131ee4546 100644 --- a/spec/lib/gitlab/cache/helpers_spec.rb +++ b/spec/lib/gitlab/cache/helpers_spec.rb @@ -33,10 +33,23 @@ RSpec.describe Gitlab::Cache::Helpers, :use_clean_rails_redis_caching do context 'single object' do let_it_be(:presentable) { create(:merge_request, source_project: project, source_branch: 'wip') } - it_behaves_like 'object cache helper' + context 'when presenter is a serializer' do + let(:expected_cache_key_prefix) { 'MergeRequestSerializer' } + + it_behaves_like 'object cache helper' + end + + context 'when presenter is a Grape::Entity' do + let(:presenter) { API::Entities::MergeRequest } + let(:expected_cache_key_prefix) { 'API::Entities::MergeRequest' } + + it_behaves_like 'object cache helper' + end end context 'collection of objects' do + let(:expected_cache_key_prefix) { 'MergeRequestSerializer' } + let_it_be(:presentable) do [ create(:merge_request, source_project: project, source_branch: 'fix'), @@ -46,5 +59,17 @@ RSpec.describe Gitlab::Cache::Helpers, :use_clean_rails_redis_caching do it_behaves_like 'collection cache helper' end + + context 'when passed presenter is not a serializer or an entity' do + let(:presenter) { User } + + let_it_be(:presentable) do + create(:merge_request, source_project: project, source_branch: 'master') + end + + it 'throws an exception' do + expect { subject }.to raise_exception(ArgumentError, "presenter User is not supported") + end + end end end diff --git a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb index b0bdbf5c371..4f347034c0b 100644 --- a/spec/lib/gitlab/database/migrations/instrumentation_spec.rb +++ b/spec/lib/gitlab/database/migrations/instrumentation_spec.rb @@ -18,6 +18,7 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do let(:migration_name) { 'test' } let(:migration_version) { '12345' } let(:migration_meta) { { 'max_batch_size' => 1, 'total_tuple_count' => 10, 'interval' => 60 } } + let(:expected_json_keys) { %w[version name walltime success total_database_size_change query_statistics] } it 'executes the given block' do expect do |b| @@ -81,7 +82,10 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do expect(subject.success).to be_truthy expect(subject.version).to eq(migration_version) expect(subject.name).to eq(migration_name) - expect(subject.meta).to eq(migration_meta) + end + + it 'transforms observation to expected json' do + expect(Gitlab::Json.parse(subject.to_json).keys).to contain_exactly(*expected_json_keys) end end @@ -114,7 +118,10 @@ RSpec.describe Gitlab::Database::Migrations::Instrumentation do expect(subject['success']).to be_falsey expect(subject['version']).to eq(migration_version) expect(subject['name']).to eq(migration_name) - expect(subject['meta']).to include(migration_meta) + end + + it 'transforms observation to expected json' do + expect(Gitlab::Json.parse(subject.to_json).keys).to contain_exactly(*expected_json_keys) end end end diff --git a/spec/lib/gitlab/database/migrations/observers/batch_details_spec.rb b/spec/lib/gitlab/database/migrations/observers/batch_details_spec.rb new file mode 100644 index 00000000000..5b3c23736ed --- /dev/null +++ b/spec/lib/gitlab/database/migrations/observers/batch_details_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Database::Migrations::Observers::BatchDetails, feature_category: :database do + subject(:observe) { described_class.new(observation, path, connection) } + + let(:connection) { ActiveRecord::Migration.connection } + let(:observation) { Gitlab::Database::Migrations::Observation.new(meta: meta) } + let(:path) { Dir.mktmpdir } + let(:file_name) { 'batch-details.json' } + let(:file_path) { Pathname.new(path).join(file_name) } + let(:json_file) { Gitlab::Json.parse(File.read(file_path)) } + let(:job_meta) do + { "min_value" => 1, "max_value" => 19, "batch_size" => 20, "sub_batch_size" => 5, "pause_ms" => 100 } + end + + where(:meta, :expected_keys) do + [ + [lazy { { job_meta: job_meta } }, %w[time_spent min_value max_value batch_size sub_batch_size pause_ms]], + [nil, %w[time_spent]], + [{ job_meta: nil }, %w[time_spent]] + ] + end + + with_them do + before do + observe.before + observe.after + end + + after do + FileUtils.remove_entry(path) + end + + it 'records expected information to file' do + observe.record + + expect(json_file.keys).to match_array(expected_keys) + end + end +end diff --git a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb index 0b048617ce1..7b569f7ce56 100644 --- a/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb +++ b/spec/lib/gitlab/database/migrations/test_batched_background_runner_spec.rb @@ -45,18 +45,15 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez end with_them do - let(:result_dir) { Dir.mktmpdir } + let(:result_dir) { Pathname.new(Dir.mktmpdir) } + let(:connection) { base_model.connection } + let(:table_name) { "_test_column_copying" } + let(:from_id) { 0 } after do FileUtils.rm_rf(result_dir) end - let(:connection) { base_model.connection } - - let(:table_name) { "_test_column_copying" } - - let(:from_id) { 0 } - before do connection.execute(<<~SQL) CREATE TABLE #{table_name} ( @@ -70,26 +67,15 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez context 'running a real background migration' do let(:interval) { 5.minutes } - let(:meta) { { "max_batch_size" => nil, "total_tuple_count" => nil, "interval" => interval } } - - let(:params) do - { - version: nil, - connection: connection, - meta: { - interval: 300, - max_batch_size: nil, - total_tuple_count: nil - } - } - end + let(:params) { { version: nil, connection: connection } } + let(:migration_name) { 'CopyColumnUsingBackgroundMigrationJob' } + let(:migration_file_path) { result_dir.join('CopyColumnUsingBackgroundMigrationJob', 'details.json') } + let(:json_file) { Gitlab::Json.parse(File.read(migration_file_path)) } + let(:expected_file_keys) { %w[interval total_tuple_count max_batch_size] } before do - queue_migration('CopyColumnUsingBackgroundMigrationJob', - table_name, :id, - :id, :data, - batch_size: 100, - job_interval: interval) # job_interval is skipped when testing + # job_interval is skipped when testing + queue_migration(migration_name, table_name, :id, :id, :data, batch_size: 100, job_interval: interval) end subject(:sample_migration) do @@ -113,6 +99,12 @@ RSpec.describe Gitlab::Database::Migrations::TestBatchedBackgroundRunner, :freez subject end + + it 'exports migration details to a file' do + subject + + expect(json_file.keys).to match_array(expected_file_keys) + end end context 'with jobs to run' do diff --git a/spec/models/ci/build_pending_state_spec.rb b/spec/models/ci/build_pending_state_spec.rb index 756180621ec..bff0b35f878 100644 --- a/spec/models/ci/build_pending_state_spec.rb +++ b/spec/models/ci/build_pending_state_spec.rb @@ -2,7 +2,14 @@ require 'spec_helper' -RSpec.describe Ci::BuildPendingState do +RSpec.describe Ci::BuildPendingState, feature_category: :continuous_integration do + describe 'validations' do + subject(:pending_state) { build(:ci_build_pending_state) } + + it { is_expected.to belong_to(:build) } + it { is_expected.to validate_presence_of(:build) } + end + describe '#crc32' do context 'when checksum does not exist' do let(:pending_state) do diff --git a/spec/services/ci/update_build_state_service_spec.rb b/spec/services/ci/update_build_state_service_spec.rb index 90a86e7ae59..f8ecff20728 100644 --- a/spec/services/ci/update_build_state_service_spec.rb +++ b/spec/services/ci/update_build_state_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe Ci::UpdateBuildStateService do +RSpec.describe Ci::UpdateBuildStateService, feature_category: :continuous_integration do let_it_be(:project) { create(:project) } let_it_be(:pipeline) { create(:ci_pipeline, project: project) } diff --git a/spec/support/rspec_order_todo.yml b/spec/support/rspec_order_todo.yml index c43dd88e3fe..f9de9eadb3f 100644 --- a/spec/support/rspec_order_todo.yml +++ b/spec/support/rspec_order_todo.yml @@ -3147,7 +3147,6 @@ - './ee/spec/views/projects/security/sast_configuration/show.html.haml_spec.rb' - './ee/spec/views/projects/settings/subscriptions/_index.html.haml_spec.rb' - './ee/spec/views/registrations/groups_projects/new.html.haml_spec.rb' -- './ee/spec/views/registrations/welcome/continuous_onboarding_getting_started.html.haml_spec.rb' - './ee/spec/views/search/_category.html.haml_spec.rb' - './ee/spec/views/shared/billings/_billing_plan_actions.html.haml_spec.rb' - './ee/spec/views/shared/billings/_billing_plan.html.haml_spec.rb' diff --git a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb index 6cdd7954b5f..42e8fa3d51f 100644 --- a/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb +++ b/spec/support/shared_examples/lib/cache_helpers_shared_examples.rb @@ -17,7 +17,7 @@ RSpec.shared_examples_for 'object cache helper' do end it "fetches from the cache" do - expect(instance.cache).to receive(:fetch).with("#{presenter.class.name}:#{presentable.cache_key}:#{user.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once + expect(instance.cache).to receive(:fetch).with("#{expected_cache_key_prefix}:#{presentable.cache_key}:#{user.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once subject end @@ -28,7 +28,7 @@ RSpec.shared_examples_for 'object cache helper' do end it "uses the context to augment the cache key" do - expect(instance.cache).to receive(:fetch).with("#{presenter.class.name}:#{presentable.cache_key}:#{project.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once + expect(instance.cache).to receive(:fetch).with("#{expected_cache_key_prefix}:#{presentable.cache_key}:#{project.cache_key}", expires_in: described_class::DEFAULT_EXPIRY).once subject end @@ -38,7 +38,7 @@ RSpec.shared_examples_for 'object cache helper' do it "sets the expiry when accessing the cache" do kwargs[:expires_in] = 7.days - expect(instance.cache).to receive(:fetch).with("#{presenter.class.name}:#{presentable.cache_key}:#{user.cache_key}", expires_in: 7.days).once + expect(instance.cache).to receive(:fetch).with("#{expected_cache_key_prefix}:#{presentable.cache_key}:#{user.cache_key}", expires_in: 7.days).once subject end @@ -90,7 +90,7 @@ RSpec.shared_examples_for 'collection cache helper' do end it "fetches from the cache" do - keys = presentable.map { |item| "#{presenter.class.name}:#{item.cache_key}:#{user.cache_key}" } + keys = presentable.map { |item| "#{expected_cache_key_prefix}:#{item.cache_key}:#{user.cache_key}" } expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: described_class::DEFAULT_EXPIRY).once.and_call_original @@ -103,7 +103,7 @@ RSpec.shared_examples_for 'collection cache helper' do end it "uses the context to augment the cache key" do - keys = presentable.map { |item| "#{presenter.class.name}:#{item.cache_key}:#{project.cache_key}" } + keys = presentable.map { |item| "#{expected_cache_key_prefix}:#{item.cache_key}:#{project.cache_key}" } expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: described_class::DEFAULT_EXPIRY).once.and_call_original @@ -113,7 +113,7 @@ RSpec.shared_examples_for 'collection cache helper' do context "expires_in is supplied" do it "sets the expiry when accessing the cache" do - keys = presentable.map { |item| "#{presenter.class.name}:#{item.cache_key}:#{user.cache_key}" } + keys = presentable.map { |item| "#{expected_cache_key_prefix}:#{item.cache_key}:#{user.cache_key}" } kwargs[:expires_in] = 7.days expect(instance.cache).to receive(:fetch_multi).with(*keys, expires_in: 7.days).once.and_call_original |