summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/CODEOWNERS4
-rw-r--r--.rubocop_todo.yml5
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue71
-rw-r--r--app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue151
-rw-r--r--app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue77
-rw-r--r--app/assets/javascripts/monitoring/constants.js10
-rw-r--r--app/assets/javascripts/monitoring/utils.js47
-rw-r--r--app/assets/stylesheets/pages/prometheus.scss14
-rw-r--r--app/finders/issuable_finder.rb9
-rw-r--r--app/models/ci/pipeline.rb2
-rw-r--r--app/models/concerns/issuable.rb28
-rw-r--r--app/models/concerns/issuable_states.rb22
-rw-r--r--app/models/concerns/milestoneish.rb6
-rw-r--r--app/models/concerns/versioned_description.rb31
-rw-r--r--app/models/concerns/worker_attributes.rb48
-rw-r--r--app/models/description_version.rb22
-rw-r--r--app/models/issue.rb13
-rw-r--r--app/models/merge_request.rb27
-rw-r--r--app/models/merge_request_diff.rb4
-rw-r--r--app/models/project_services/hipchat_service.rb2
-rw-r--r--app/models/push_event.rb2
-rw-r--r--app/models/system_note_metadata.rb1
-rw-r--r--app/serializers/projects/serverless/service_entity.rb2
-rw-r--r--app/services/issuable/clone/content_rewriter.rb4
-rw-r--r--app/services/note_summary.rb4
-rw-r--r--app/uploaders/object_storage.rb9
-rw-r--r--app/views/projects/issues/index.html.haml3
-rw-r--r--app/views/shared/empty_states/_issues.html.haml3
-rw-r--r--app/workers/admin_email_worker.rb2
-rw-r--r--app/workers/authorized_projects_worker.rb2
-rw-r--r--app/workers/auto_merge_process_worker.rb1
-rw-r--r--app/workers/background_migration_worker.rb2
-rw-r--r--app/workers/build_hooks_worker.rb1
-rw-r--r--app/workers/build_queue_worker.rb1
-rw-r--r--app/workers/chat_notification_worker.rb2
-rw-r--r--app/workers/ci/archive_traces_cron_worker.rb2
-rw-r--r--app/workers/ci/build_prepare_worker.rb1
-rw-r--r--app/workers/ci/build_schedule_worker.rb1
-rw-r--r--app/workers/cleanup_container_repository_worker.rb1
-rw-r--r--app/workers/concerns/application_worker.rb1
-rw-r--r--app/workers/concerns/auto_devops_queue.rb1
-rw-r--r--app/workers/concerns/chaos_queue.rb1
-rw-r--r--app/workers/concerns/cluster_queue.rb1
-rw-r--r--app/workers/concerns/gitlab/github_import/object_importer.rb2
-rw-r--r--app/workers/concerns/gitlab/github_import/queue.rb1
-rw-r--r--app/workers/concerns/object_pool_queue.rb1
-rw-r--r--app/workers/concerns/pipeline_background_queue.rb1
-rw-r--r--app/workers/concerns/pipeline_queue.rb1
-rw-r--r--app/workers/concerns/repository_check_queue.rb2
-rw-r--r--app/workers/concerns/todos_destroyer_queue.rb1
-rw-r--r--app/workers/create_evidence_worker.rb2
-rw-r--r--app/workers/create_gpg_signature_worker.rb2
-rw-r--r--app/workers/create_note_diff_file_worker.rb2
-rw-r--r--app/workers/create_pipeline_worker.rb1
-rw-r--r--app/workers/delete_container_repository_worker.rb1
-rw-r--r--app/workers/delete_diff_files_worker.rb2
-rw-r--r--app/workers/delete_merged_branches_worker.rb2
-rw-r--r--app/workers/delete_stored_files_worker.rb2
-rw-r--r--app/workers/delete_user_worker.rb2
-rw-r--r--app/workers/deployments/finished_worker.rb1
-rw-r--r--app/workers/deployments/success_worker.rb1
-rw-r--r--app/workers/detect_repository_languages_worker.rb1
-rw-r--r--app/workers/email_receiver_worker.rb2
-rw-r--r--app/workers/emails_on_push_worker.rb2
-rw-r--r--app/workers/expire_build_artifacts_worker.rb2
-rw-r--r--app/workers/expire_build_instance_artifacts_worker.rb2
-rw-r--r--app/workers/git_garbage_collect_worker.rb1
-rw-r--r--app/workers/gitlab/github_import/advance_stage_worker.rb1
-rw-r--r--app/workers/gitlab_shell_worker.rb2
-rw-r--r--app/workers/gitlab_usage_ping_worker.rb2
-rw-r--r--app/workers/group_destroy_worker.rb2
-rw-r--r--app/workers/hashed_storage/base_worker.rb3
-rw-r--r--app/workers/hashed_storage/migrator_worker.rb1
-rw-r--r--app/workers/hashed_storage/rollbacker_worker.rb1
-rw-r--r--app/workers/import_export_project_cleanup_worker.rb2
-rw-r--r--app/workers/import_issues_csv_worker.rb2
-rw-r--r--app/workers/invalid_gpg_signature_update_worker.rb2
-rw-r--r--app/workers/irker_worker.rb2
-rw-r--r--app/workers/issue_due_scheduler_worker.rb2
-rw-r--r--app/workers/mail_scheduler/issue_due_worker.rb2
-rw-r--r--app/workers/mail_scheduler/notification_service_worker.rb2
-rw-r--r--app/workers/merge_worker.rb2
-rw-r--r--app/workers/migrate_external_diffs_worker.rb2
-rw-r--r--app/workers/namespaceless_project_destroy_worker.rb4
-rw-r--r--app/workers/namespaces/prune_aggregation_schedules_worker.rb2
-rw-r--r--app/workers/namespaces/root_statistics_worker.rb1
-rw-r--r--app/workers/namespaces/schedule_aggregation_worker.rb1
-rw-r--r--app/workers/new_issue_worker.rb2
-rw-r--r--app/workers/new_merge_request_worker.rb2
-rw-r--r--app/workers/new_note_worker.rb2
-rw-r--r--app/workers/new_release_worker.rb1
-rw-r--r--app/workers/object_storage/background_move_worker.rb1
-rw-r--r--app/workers/object_storage/migrate_uploads_worker.rb2
-rw-r--r--app/workers/pages_domain_removal_cron_worker.rb2
-rw-r--r--app/workers/pages_domain_ssl_renewal_cron_worker.rb2
-rw-r--r--app/workers/pages_domain_ssl_renewal_worker.rb2
-rw-r--r--app/workers/pages_domain_verification_cron_worker.rb2
-rw-r--r--app/workers/pages_domain_verification_worker.rb2
-rw-r--r--app/workers/pages_worker.rb1
-rw-r--r--app/workers/pipeline_process_worker.rb1
-rw-r--r--app/workers/pipeline_schedule_worker.rb2
-rw-r--r--app/workers/plugin_worker.rb1
-rw-r--r--app/workers/post_receive.rb2
-rw-r--r--app/workers/process_commit_worker.rb2
-rw-r--r--app/workers/project_cache_worker.rb2
-rw-r--r--app/workers/project_daily_statistics_worker.rb2
-rw-r--r--app/workers/project_destroy_worker.rb2
-rw-r--r--app/workers/project_export_worker.rb1
-rw-r--r--app/workers/project_service_worker.rb1
-rw-r--r--app/workers/propagate_service_template_worker.rb2
-rw-r--r--app/workers/prune_old_events_worker.rb2
-rw-r--r--app/workers/prune_web_hook_logs_worker.rb2
-rw-r--r--app/workers/reactive_caching_worker.rb2
-rw-r--r--app/workers/rebase_worker.rb2
-rw-r--r--app/workers/remote_mirror_notification_worker.rb2
-rw-r--r--app/workers/remove_expired_group_links_worker.rb2
-rw-r--r--app/workers/remove_expired_members_worker.rb2
-rw-r--r--app/workers/remove_unreferenced_lfs_objects_worker.rb2
-rw-r--r--app/workers/repository_archive_cache_worker.rb2
-rw-r--r--app/workers/repository_check/dispatch_worker.rb2
-rw-r--r--app/workers/repository_cleanup_worker.rb1
-rw-r--r--app/workers/repository_fork_worker.rb2
-rw-r--r--app/workers/repository_import_worker.rb2
-rw-r--r--app/workers/repository_remove_remote_worker.rb2
-rw-r--r--app/workers/repository_update_remote_mirror_worker.rb1
-rw-r--r--app/workers/requests_profiles_worker.rb2
-rw-r--r--app/workers/run_pipeline_schedule_worker.rb1
-rw-r--r--app/workers/schedule_migrate_external_diffs_worker.rb2
-rw-r--r--app/workers/stuck_ci_jobs_worker.rb2
-rw-r--r--app/workers/stuck_import_jobs_worker.rb2
-rw-r--r--app/workers/stuck_merge_jobs_worker.rb4
-rw-r--r--app/workers/system_hook_push_worker.rb2
-rw-r--r--app/workers/trending_projects_worker.rb2
-rw-r--r--app/workers/update_external_pull_requests_worker.rb2
-rw-r--r--app/workers/update_head_pipeline_for_merge_request_worker.rb1
-rw-r--r--app/workers/update_merge_requests_worker.rb2
-rw-r--r--app/workers/update_project_statistics_worker.rb2
-rw-r--r--app/workers/upload_checksum_worker.rb2
-rw-r--r--app/workers/web_hook_worker.rb1
-rw-r--r--changelogs/unreleased/14064-commit-status-on-any-pipelines.yml5
-rw-r--r--changelogs/unreleased/28211-check-if-mapping-is-empty-before-caching.yml5
-rw-r--r--changelogs/unreleased/29513-continue-improvements-for-time-window-filtering-on-metrics-dashboar.yml5
-rw-r--r--changelogs/unreleased/ac-fix-only-os-uplods.yml5
-rw-r--r--changelogs/unreleased/an-sidekiq-job-feature-attribution.yml5
-rw-r--r--changelogs/unreleased/issue_28457.yml5
-rw-r--r--changelogs/unreleased/stop-showing-new-issue-button-when-project-is-archived.yml5
-rw-r--r--config/feature_categories.yml103
-rw-r--r--db/migrate/20190927055500_create_description_versions.rb29
-rw-r--r--db/migrate/20190927055540_add_index_to_sytem_note_metadata_description_version_id.rb19
-rw-r--r--db/migrate/20191008180203_add_issuable_state_id_indexes.rb75
-rw-r--r--db/migrate/20191008200204_add_state_id_default_value.rb24
-rw-r--r--db/schema.rb31
-rw-r--r--doc/api/issues.md2
-rw-r--r--doc/development/sidekiq_style_guide.md50
-rw-r--r--doc/development/testing_guide/flaky_tests.md3
-rw-r--r--doc/user/analytics/productivity_analytics.md2
-rw-r--r--lib/api/commit_statuses.rb20
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb2
-rw-r--r--lib/gitlab/discussions_diff/file_collection.rb4
-rw-r--r--lib/gitlab/google_code_import/importer.rb2
-rw-r--r--lib/gitlab/hook_data/issue_builder.rb5
-rw-r--r--lib/gitlab/import_export/relation_factory.rb6
-rw-r--r--lib/gitlab/phabricator_import/base_worker.rb2
-rw-r--r--lib/uploaded_file.rb54
-rw-r--r--locale/gitlab.pot21
-rw-r--r--qa/spec/page/element_spec.rb1
-rwxr-xr-xscripts/update-feature-categories36
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb1
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb1
-rw-r--r--spec/controllers/projects/serverless/functions_controller_spec.rb134
-rw-r--r--spec/controllers/projects/settings/ci_cd_controller_spec.rb1
-rw-r--r--spec/factories/gitaly/commit.rb1
-rw-r--r--spec/factories/issues.rb8
-rw-r--r--spec/factories/merge_requests.rb12
-rw-r--r--spec/features/groups/issues_spec.rb1
-rw-r--r--spec/features/security/group/internal_access_spec.rb1
-rw-r--r--spec/features/security/group/private_access_spec.rb1
-rw-r--r--spec/features/security/group/public_access_spec.rb1
-rw-r--r--spec/features/security/project/internal_access_spec.rb1
-rw-r--r--spec/features/security/project/private_access_spec.rb1
-rw-r--r--spec/features/security/project/public_access_spec.rb1
-rw-r--r--spec/finders/clusters/knative_services_finder_spec.rb1
-rw-r--r--spec/finders/concerns/finder_with_cross_project_access_spec.rb1
-rw-r--r--spec/fixtures/lib/gitlab/import_export/project.group.json2
-rw-r--r--spec/frontend/monitoring/components/date_time_picker/date_time_picker_input_spec.js66
-rw-r--r--spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js157
-rw-r--r--spec/helpers/merge_requests_helper_spec.rb1
-rw-r--r--spec/javascripts/monitoring/components/dashboard_spec.js32
-rw-r--r--spec/javascripts/monitoring/utils_spec.js206
-rw-r--r--spec/lib/backup/files_spec.rb1
-rw-r--r--spec/lib/banzai/filter/project_reference_filter_spec.rb1
-rw-r--r--spec/lib/banzai/filter/user_reference_filter_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/commit_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/commit_range_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/external_issue_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/label_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/merge_request_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/milestone_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/project_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/snippet_parser_spec.rb1
-rw-r--r--spec/lib/banzai/reference_parser/user_parser_spec.rb1
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb15
-rw-r--r--spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb4
-rw-r--r--spec/lib/gitlab/ci/config/entry/cache_spec.rb1
-rw-r--r--spec/lib/gitlab/ci/config/entry/coverage_spec.rb7
-rw-r--r--spec/lib/gitlab/cleanup/project_uploads_spec.rb1
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb1
-rw-r--r--spec/lib/gitlab/discussions_diff/file_collection_spec.rb8
-rw-r--r--spec/lib/gitlab/downtime_check_spec.rb1
-rw-r--r--spec/lib/gitlab/email/message/repository_push_spec.rb18
-rw-r--r--spec/lib/gitlab/experimentation_spec.rb1
-rw-r--r--spec/lib/gitlab/gfm/uploads_rewriter_spec.rb2
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb1
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb9
-rw-r--r--spec/lib/gitlab/git/diff_collection_spec.rb51
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb3
-rw-r--r--spec/lib/gitlab/gitlab_import/client_spec.rb3
-rw-r--r--spec/lib/gitlab/graphs/commits_spec.rb3
-rw-r--r--spec/lib/gitlab/health_checks/gitaly_check_spec.rb1
-rw-r--r--spec/lib/gitlab/health_checks/simple_check_shared.rb2
-rw-r--r--spec/lib/gitlab/hook_data/issue_builder_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml2
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb5
-rw-r--r--spec/lib/gitlab/import_export/relation_factory_spec.rb2
-rw-r--r--spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb1
-rw-r--r--spec/lib/gitlab/phabricator_import/worker_state_spec.rb1
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb1
-rw-r--r--spec/lib/gitlab/request_context_spec.rb1
-rw-r--r--spec/lib/gitlab/shell_spec.rb1
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb1
-rw-r--r--spec/lib/gitlab/utils/override_spec.rb6
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb2
-rw-r--r--spec/lib/json_web_token/token_spec.rb1
-rw-r--r--spec/lib/omni_auth/strategies/jwt_spec.rb1
-rw-r--r--spec/lib/uploaded_file_spec.rb56
-rw-r--r--spec/models/ci/build_spec.rb3
-rw-r--r--spec/models/ci/runner_spec.rb2
-rw-r--r--spec/models/concerns/noteable_spec.rb1
-rw-r--r--spec/models/concerns/token_authenticatable_spec.rb1
-rw-r--r--spec/models/deploy_keys_project_spec.rb1
-rw-r--r--spec/models/description_version_spec.rb29
-rw-r--r--spec/models/event_spec.rb5
-rw-r--r--spec/models/gpg_signature_spec.rb1
-rw-r--r--spec/models/issue_spec.rb10
-rw-r--r--spec/models/merge_request_spec.rb26
-rw-r--r--spec/models/note_spec.rb10
-rw-r--r--spec/models/pages_domain_spec.rb2
-rw-r--r--spec/models/project_spec.rb1
-rw-r--r--spec/models/project_wiki_spec.rb2
-rw-r--r--spec/models/resource_label_event_spec.rb1
-rw-r--r--spec/models/system_note_metadata_spec.rb1
-rw-r--r--spec/models/timelog_spec.rb1
-rw-r--r--spec/models/user_interacted_project_spec.rb1
-rw-r--r--spec/models/user_spec.rb1
-rw-r--r--spec/policies/identity_provider_policy_spec.rb1
-rw-r--r--spec/policies/merge_request_policy_spec.rb6
-rw-r--r--spec/presenters/conversational_development_index/metric_presenter_spec.rb1
-rw-r--r--spec/requests/api/commit_statuses_spec.rb60
-rw-r--r--spec/requests/api/graphql/namespace/projects_spec.rb1
-rw-r--r--spec/requests/api/pages/internal_access_spec.rb1
-rw-r--r--spec/requests/api/pages/private_access_spec.rb1
-rw-r--r--spec/requests/api/pages/public_access_spec.rb1
-rw-r--r--spec/requests/api/runner_spec.rb25
-rw-r--r--spec/requests/lfs_http_spec.rb54
-rw-r--r--spec/rubocop/cop/migration/add_timestamps_spec.rb1
-rw-r--r--spec/rubocop/cop/migration/timestamps_spec.rb1
-rw-r--r--spec/rubocop/cop/scalability/file_uploads_spec.rb1
-rw-r--r--spec/serializers/cluster_basic_entity_spec.rb1
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb1
-rw-r--r--spec/services/git/tag_hooks_service_spec.rb1
-rw-r--r--spec/services/merge_requests/create_from_issue_service_spec.rb1
-rw-r--r--spec/services/note_summary_spec.rb12
-rw-r--r--spec/services/projects/housekeeping_service_spec.rb1
-rw-r--r--spec/services/system_notes/issuables_service_spec.rb9
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb25
-rw-r--r--spec/support/helpers/workhorse_helpers.rb55
-rw-r--r--spec/support/redis/redis_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb2
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb1
-rw-r--r--spec/support/shared_examples/versioned_description_shared_examples.rb65
-rw-r--r--spec/tasks/gitlab/check_rake_spec.rb6
-rw-r--r--spec/workers/every_sidekiq_worker_spec.rb28
-rw-r--r--spec/workers/hashed_storage/migrator_worker_spec.rb1
-rw-r--r--spec/workers/hashed_storage/rollbacker_worker_spec.rb1
-rw-r--r--[-rwxr-xr-x]vendor/gitignore/C++.gitignore0
-rw-r--r--[-rwxr-xr-x]vendor/gitignore/Java.gitignore0
287 files changed, 2376 insertions, 287 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 6be92710ad9..a02740373da 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -6,8 +6,8 @@
/doc/ @axil @marcia @eread @mikelewis
# Frontend maintainers should see everything in `app/assets/`
-app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi
-*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi
+app/assets/ @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina
+*.scss @annabeldunstone @ClemMakesApps @fatihacet @filipa @mikegreiling @timzallmann @kushalpandya @pslaughter @wortschi @ntepluhina
# Database maintainers should review changes in `db/`
db/ @gitlab-org/maintainers/database
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 81bb28dacab..3ed7af71b4f 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -273,11 +273,6 @@ RSpec/ContextWording:
RSpec/EmptyLineAfterFinalLet:
Enabled: false
-# Offense count: 232
-# Cop supports --auto-correct.
-RSpec/EmptyLineAfterSubject:
- Enabled: false
-
# Offense count: 719
# Cop supports --auto-correct.
# Configuration parameters: EnforcedStyle.
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 2e35ef8d4b0..b4ea415bb51 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -12,23 +12,19 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import { __, s__ } from '~/locale';
+import createFlash from '~/flash';
import Icon from '~/vue_shared/components/icon.vue';
-import { getParameterValues, mergeUrlParams } from '~/lib/utils/url_utility';
+import { getParameterValues, mergeUrlParams, redirectTo } from '~/lib/utils/url_utility';
import invalidUrl from '~/lib/utils/invalid_url';
import PanelType from 'ee_else_ce/monitoring/components/panel_type.vue';
+import DateTimePicker from './date_time_picker/date_time_picker.vue';
import MonitorTimeSeriesChart from './charts/time_series.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
import GraphGroup from './graph_group.vue';
import EmptyState from './empty_state.vue';
-import { sidebarAnimationDuration, timeWindows } from '../constants';
+import { sidebarAnimationDuration } from '../constants';
import TrackEventDirective from '~/vue_shared/directives/track_event';
-
-import {
- getTimeDiff,
- getTimeWindow,
- downloadCSVOptions,
- generateLinkToChartOptions,
-} from '../utils';
+import { getTimeDiff, isValidDate, downloadCSVOptions, generateLinkToChartOptions } from '../utils';
let sidebarMutationObserver;
@@ -46,6 +42,7 @@ export default {
GlDropdownItem,
GlFormGroup,
GlModal,
+ DateTimePicker,
},
directives: {
GlModal: GlModalDirective,
@@ -171,10 +168,8 @@ export default {
return {
state: 'gettingStarted',
elWidth: 0,
- selectedTimeWindow: '',
- selectedTimeWindowKey: '',
formIsValid: null,
- timeWindows: {},
+ selectedTimeWindow: {},
isRearrangingPanels: false,
};
},
@@ -237,11 +232,13 @@ export default {
end,
};
- this.timeWindows = timeWindows;
- this.selectedTimeWindowKey = getTimeWindow(range);
- this.selectedTimeWindow = this.timeWindows[this.selectedTimeWindowKey];
+ this.selectedTimeWindow = range;
- this.fetchData(range);
+ if (!isValidDate(start) || !isValidDate(end)) {
+ this.showInvalidDateError();
+ } else {
+ this.fetchData(range);
+ }
sidebarMutationObserver = new MutationObserver(this.onSidebarMutation);
sidebarMutationObserver.observe(document.querySelector('.layout-page'), {
@@ -298,6 +295,9 @@ export default {
// See https://gitlab.com/gitlab-org/gitlab/issues/27835
metrics.splice(graphIndex, 1);
},
+ showInvalidDateError() {
+ createFlash(s__('Metrics|Link contains an invalid time window.'));
+ },
generateLink(group, title, yLabel) {
const dashboard = this.currentDashboard || this.firstDashboard.path;
const params = _.pick({ dashboard, group, title, y_label: yLabel }, value => value != null);
@@ -320,16 +320,12 @@ export default {
submitCustomMetricsForm() {
this.$refs.customMetricsForm.submit();
},
- activeTimeWindow(key) {
- return this.timeWindows[key] === this.selectedTimeWindow;
- },
- setTimeWindowParameter(key) {
- const { start, end } = getTimeDiff(key);
- return `?start=${encodeURIComponent(start)}&end=${encodeURIComponent(end)}`;
- },
groupHasData(group) {
return this.chartsWithData(group.metrics).length > 0;
},
+ onDateTimePickerApply(timeWindowUrlParams) {
+ return redirectTo(mergeUrlParams(timeWindowUrlParams, window.location.href));
+ },
downloadCSVOptions,
generateLinkToChartOptions,
},
@@ -342,14 +338,14 @@ export default {
<template>
<div class="prometheus-graphs">
- <div class="gl-p-3 pb-0 border-bottom bg-gray-light">
+ <div class="prometheus-graphs-header gl-p-3 pb-0 border-bottom bg-gray-light">
<div class="row">
<template v-if="environmentsEndpoint">
<gl-form-group
:label="__('Dashboard')"
label-size="sm"
label-for="monitor-dashboards-dropdown"
- class="col-sm-12 col-md-4 col-lg-2"
+ class="col-sm-12 col-md-6 col-lg-2"
>
<gl-dropdown
id="monitor-dashboards-dropdown"
@@ -372,7 +368,7 @@ export default {
:label="s__('Metrics|Environment')"
label-size="sm"
label-for="monitor-environments-dropdown"
- class="col-sm-6 col-md-4 col-lg-2"
+ class="col-sm-6 col-md-6 col-lg-2"
>
<gl-dropdown
id="monitor-environments-dropdown"
@@ -397,30 +393,19 @@ export default {
:label="s__('Metrics|Show last')"
label-size="sm"
label-for="monitor-time-window-dropdown"
- class="col-sm-6 col-md-4 col-lg-2"
+ class="col-sm-6 col-md-6 col-lg-4"
>
- <gl-dropdown
- id="monitor-time-window-dropdown"
- class="mb-0 d-flex js-time-window-dropdown"
- toggle-class="dropdown-menu-toggle"
- :text="selectedTimeWindow"
- >
- <gl-dropdown-item
- v-for="(value, key) in timeWindows"
- :key="key"
- :active="activeTimeWindow(key)"
- :href="setTimeWindowParameter(key)"
- active-class="active"
- >{{ value }}</gl-dropdown-item
- >
- </gl-dropdown>
+ <date-time-picker
+ :selected-time-window="selectedTimeWindow"
+ @onApply="onDateTimePickerApply"
+ />
</gl-form-group>
</template>
<gl-form-group
v-if="addingMetricsAvailable || showRearrangePanelsBtn || externalDashboardUrl.length"
label-for="prometheus-graphs-dropdown-buttons"
- class="dropdown-buttons col-lg d-lg-flex align-items-end"
+ class="dropdown-buttons col-md d-md-flex col-lg d-lg-flex align-items-end"
>
<div id="prometheus-graphs-dropdown-buttons">
<gl-button
diff --git a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue
new file mode 100644
index 00000000000..4616a767295
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker.vue
@@ -0,0 +1,151 @@
+<script>
+import { GlButton, GlDropdown, GlDropdownItem, GlFormGroup } from '@gitlab/ui';
+import { s__, sprintf } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+import DateTimePickerInput from './date_time_picker_input.vue';
+import {
+ getTimeDiff,
+ getTimeWindow,
+ stringToISODate,
+ ISODateToString,
+ truncateZerosInDateTime,
+ isDateTimePickerInputValid,
+} from '~/monitoring/utils';
+import { timeWindows } from '~/monitoring/constants';
+
+export default {
+ components: {
+ Icon,
+ DateTimePickerInput,
+ GlFormGroup,
+ GlButton,
+ GlDropdown,
+ GlDropdownItem,
+ },
+ props: {
+ timeWindows: {
+ type: Object,
+ required: false,
+ default: () => timeWindows,
+ },
+ selectedTimeWindow: {
+ type: Object,
+ required: false,
+ default: () => {},
+ },
+ },
+ data() {
+ return {
+ selectedTimeWindowText: '',
+ customTime: {
+ from: null,
+ to: null,
+ },
+ };
+ },
+ computed: {
+ applyEnabled() {
+ return Boolean(this.inputState.from && this.inputState.to);
+ },
+ inputState() {
+ const { from, to } = this.customTime;
+ return {
+ from: from && isDateTimePickerInputValid(from),
+ to: to && isDateTimePickerInputValid(to),
+ };
+ },
+ },
+ mounted() {
+ const range = getTimeWindow(this.selectedTimeWindow);
+ if (range) {
+ this.selectedTimeWindowText = this.timeWindows[range];
+ } else {
+ this.customTime = {
+ from: truncateZerosInDateTime(ISODateToString(this.selectedTimeWindow.start)),
+ to: truncateZerosInDateTime(ISODateToString(this.selectedTimeWindow.end)),
+ };
+ this.selectedTimeWindowText = sprintf(s__('%{from} to %{to}'), this.customTime);
+ }
+ },
+ methods: {
+ activeTimeWindow(key) {
+ return this.timeWindows[key] === this.selectedTimeWindowText;
+ },
+ setCustomTimeWindowParameter() {
+ this.$emit('onApply', {
+ start: stringToISODate(this.customTime.from),
+ end: stringToISODate(this.customTime.to),
+ });
+ },
+ setTimeWindowParameter(key) {
+ const { start, end } = getTimeDiff(key);
+ this.$emit('onApply', {
+ start,
+ end,
+ });
+ },
+ closeDropdown() {
+ this.$refs.dropdown.hide();
+ },
+ },
+};
+</script>
+<template>
+ <gl-dropdown
+ ref="dropdown"
+ :text="selectedTimeWindowText"
+ menu-class="time-window-dropdown-menu"
+ class="js-time-window-dropdown"
+ >
+ <div class="d-flex justify-content-between time-window-dropdown-menu-container">
+ <gl-form-group
+ :label="__('Custom range')"
+ label-for="custom-from-time"
+ class="custom-time-range-form-group col-md-7 p-0 m-0"
+ >
+ <date-time-picker-input
+ id="custom-time-from"
+ v-model="customTime.from"
+ :label="__('From')"
+ :state="inputState.from"
+ />
+ <date-time-picker-input
+ id="custom-time-to"
+ v-model="customTime.to"
+ :label="__('To')"
+ :state="inputState.to"
+ />
+ <gl-form-group>
+ <gl-button @click="closeDropdown">{{ __('Cancel') }}</gl-button>
+ <gl-button
+ variant="success"
+ :disabled="!applyEnabled"
+ @click="setCustomTimeWindowParameter"
+ >{{ __('Apply') }}</gl-button
+ >
+ </gl-form-group>
+ </gl-form-group>
+ <gl-form-group
+ :label="__('Quick range')"
+ label-for="group-id-dropdown"
+ label-align="center"
+ class="col-md-4 p-0 m-0"
+ >
+ <gl-dropdown-item
+ v-for="(value, key) in timeWindows"
+ :key="key"
+ :active="activeTimeWindow(key)"
+ active-class="active"
+ @click="setTimeWindowParameter(key)"
+ >
+ <icon
+ name="mobile-issue-close"
+ class="align-bottom"
+ :class="{ invisible: !activeTimeWindow(key) }"
+ />
+ {{ value }}
+ </gl-dropdown-item>
+ </gl-form-group>
+ </div>
+ </gl-dropdown>
+</template>
diff --git a/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue
new file mode 100644
index 00000000000..0388a6190d9
--- /dev/null
+++ b/app/assets/javascripts/monitoring/components/date_time_picker/date_time_picker_input.vue
@@ -0,0 +1,77 @@
+<script>
+import _ from 'underscore';
+import { s__, sprintf } from '~/locale';
+import { GlFormGroup, GlFormInput } from '@gitlab/ui';
+import { dateFormats } from '~/monitoring/constants';
+
+const inputGroupText = {
+ invalidFeedback: sprintf(s__('Format: %{dateFormat}'), {
+ dateFormat: dateFormats.dateTimePicker.format,
+ }),
+ placeholder: dateFormats.dateTimePicker.format,
+};
+
+export default {
+ components: {
+ GlFormGroup,
+ GlFormInput,
+ },
+ props: {
+ state: {
+ default: null,
+ required: true,
+ validator: prop => typeof prop === 'boolean' || prop === null,
+ },
+ value: {
+ default: null,
+ required: false,
+ validator: prop => typeof prop === 'string' || prop === null,
+ },
+ label: {
+ type: String,
+ default: '',
+ required: true,
+ },
+ id: {
+ type: String,
+ required: false,
+ default: () => _.uniqueId('dateTimePicker_'),
+ },
+ },
+ data() {
+ return {
+ inputGroupText,
+ };
+ },
+ computed: {
+ invalidFeedback() {
+ return this.state ? '' : this.inputGroupText.invalidFeedback;
+ },
+ inputState() {
+ // When the state is valid we want to show no
+ // green outline. Hence passing null and not true.
+ if (this.state === true) {
+ return null;
+ }
+ return this.state;
+ },
+ },
+ methods: {
+ onInputBlur(e) {
+ this.$emit('input', e.target.value.trim() || null);
+ },
+ },
+};
+</script>
+
+<template>
+ <gl-form-group :label="label" label-size="sm" :label-for="id" :invalid-feedback="invalidFeedback">
+ <gl-form-input
+ :id="id"
+ :value="value"
+ :state="inputState"
+ :placeholder="inputGroupText.placeholder"
+ @blur="onInputBlur"
+ />
+ </gl-form-group>
+</template>
diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js
index 13aba3d9f44..2836fe4fc26 100644
--- a/app/assets/javascripts/monitoring/constants.js
+++ b/app/assets/javascripts/monitoring/constants.js
@@ -3,6 +3,11 @@ import { __ } from '~/locale';
export const sidebarAnimationDuration = 300; // milliseconds.
export const chartHeight = 300;
+/**
+ * Valid strings for this regex are
+ * 2019-10-01 and 2019-10-01 01:02:03
+ */
+export const dateTimePickerRegex = /^(\d{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])(?: (0[0-9]|1[0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]))?$/;
export const graphTypes = {
deploymentData: 'scatter',
@@ -28,6 +33,11 @@ export const timeWindows = {
export const dateFormats = {
timeOfDay: 'h:MM TT',
default: 'dd mmm yyyy, h:MMTT',
+ dateTimePicker: {
+ format: 'yyyy-mm-dd hh:mm:ss',
+ ISODate: "yyyy-mm-dd'T'HH:MM:ss'Z'",
+ stringDate: 'yyyy-mm-dd HH:MM:ss',
+ },
};
export const secondsIn = {
diff --git a/app/assets/javascripts/monitoring/utils.js b/app/assets/javascripts/monitoring/utils.js
index 9049695b992..4c72f5226b7 100644
--- a/app/assets/javascripts/monitoring/utils.js
+++ b/app/assets/javascripts/monitoring/utils.js
@@ -1,4 +1,5 @@
-import { secondsIn, timeWindowsKeyNames } from './constants';
+import dateformat from 'dateformat';
+import { secondsIn, dateTimePickerRegex, dateFormats } from './constants';
const secondsToMilliseconds = seconds => seconds * 1000;
@@ -19,7 +20,49 @@ export const getTimeWindow = ({ start, end }) =>
return timeRange;
}
return acc;
- }, timeWindowsKeyNames.eightHours);
+ }, null);
+
+export const isDateTimePickerInputValid = val => dateTimePickerRegex.test(val);
+
+export const truncateZerosInDateTime = datetime => datetime.replace(' 00:00:00', '');
+
+/**
+ * The URL params start and end need to be validated
+ * before passing them down to other components.
+ *
+ * @param {string} dateString
+ */
+export const isValidDate = dateString => {
+ try {
+ // dateformat throws error that can be caught.
+ // This is better than using `new Date()`
+ if (dateString && dateString.trim()) {
+ dateformat(dateString, 'isoDateTime');
+ return true;
+ }
+ return false;
+ } catch {
+ return false;
+ }
+};
+
+/**
+ * Convert the input in Time picker component to ISO date.
+ *
+ * @param {string} val
+ * @returns {string}
+ */
+export const stringToISODate = val =>
+ dateformat(new Date(val.replace(/-/g, '/')), dateFormats.dateTimePicker.ISODate, true);
+
+/**
+ * Convert the ISO date received from the URL to string
+ * for the Time picker component.
+ *
+ * @param {Date} date
+ * @returns {string}
+ */
+export const ISODateToString = date => dateformat(date, dateFormats.dateTimePicker.stringDate);
/**
* This method is used to validate if the graph data format for a chart component
diff --git a/app/assets/stylesheets/pages/prometheus.scss b/app/assets/stylesheets/pages/prometheus.scss
index ceafff94719..154e505f7a4 100644
--- a/app/assets/stylesheets/pages/prometheus.scss
+++ b/app/assets/stylesheets/pages/prometheus.scss
@@ -46,6 +46,20 @@
}
}
+.prometheus-graphs-header {
+ .time-window-dropdown-menu {
+ padding: $gl-padding $gl-padding 0 $gl-padding-12;
+ }
+
+ .time-window-dropdown-menu-container {
+ width: 360px;
+ }
+
+ .custom-time-range-form-group > label {
+ padding-bottom: $gl-padding;
+ }
+}
+
.prometheus-panel {
margin-top: 20px;
}
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 2364777cdc5..477093ddadf 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -161,7 +161,7 @@ class IssuableFinder
labels_count = label_names.any? ? label_names.count : 1
labels_count = 1 if use_cte_for_search?
- finder.execute.reorder(nil).group(:state).count.each do |key, value|
+ finder.execute.reorder(nil).group(:state_id).count.each do |key, value|
counts[count_key(key)] += value / labels_count
end
@@ -385,7 +385,8 @@ class IssuableFinder
end
def count_key(value)
- Array(value).last.to_sym
+ value = Array(value).last
+ klass.available_states.key(value)
end
# Negates all params found in `negatable_params`
@@ -444,7 +445,6 @@ class IssuableFinder
items
end
- # rubocop: disable CodeReuse/ActiveRecord
def by_state(items)
case params[:state].to_s
when 'closed'
@@ -454,12 +454,11 @@ class IssuableFinder
when 'opened'
items.opened
when 'locked'
- items.where(state: 'locked')
+ items.with_state(:locked)
else
items
end
end
- # rubocop: enable CodeReuse/ActiveRecord
def by_group(items)
# Selection by group is already covered by `by_project` and `projects`
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 5e52062ef40..3bf19399cec 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -217,6 +217,8 @@ module Ci
scope :for_sha, -> (sha) { where(sha: sha) }
scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) }
scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) }
+ scope :for_ref, -> (ref) { where(ref: ref) }
+ scope :for_id, -> (id) { where(id: id) }
scope :created_after, -> (time) { where('ci_pipelines.created_at > ?', time) }
scope :triggered_by_merge_request, -> (merge_request) do
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 94a0de556f4..852576dbbc2 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -25,12 +25,20 @@ module Issuable
include UpdatedAtFilterable
include IssuableStates
include ClosedAtFilterable
+ include VersionedDescription
TITLE_LENGTH_MAX = 255
TITLE_HTML_LENGTH_MAX = 800
DESCRIPTION_LENGTH_MAX = 1.megabyte
DESCRIPTION_HTML_LENGTH_MAX = 5.megabytes
+ STATE_ID_MAP = {
+ opened: 1,
+ closed: 2,
+ merged: 3,
+ locked: 4
+ }.with_indifferent_access.freeze
+
# This object is used to gather issuable meta data for displaying
# upvotes, downvotes, notes and closing merge requests count for issues and merge requests
# lists avoiding n+1 queries and improving performance.
@@ -172,13 +180,17 @@ module Issuable
fuzzy_search(query, [:title])
end
- # Available state values persisted in state_id column using state machine
+ def available_states
+ @available_states ||= STATE_ID_MAP.slice(*available_state_names)
+ end
+
+ # Available state names used to persist state_id column using state machine
#
# Override this on subclasses if different states are needed
#
- # Check MergeRequest.available_states for example
- def available_states
- @available_states ||= { opened: 1, closed: 2 }.with_indifferent_access
+ # Check MergeRequest.available_states_names for example
+ def available_state_names
+ [:opened, :closed]
end
# Searches for records with a matching title or description.
@@ -297,6 +309,14 @@ module Issuable
end
end
+ def state
+ self.class.available_states.key(state_id)
+ end
+
+ def state=(value)
+ self.state_id = self.class.available_states[value]
+ end
+
def resource_parent
project
end
diff --git a/app/models/concerns/issuable_states.rb b/app/models/concerns/issuable_states.rb
index 33bc41d7f44..f0b9f0d1f3a 100644
--- a/app/models/concerns/issuable_states.rb
+++ b/app/models/concerns/issuable_states.rb
@@ -4,22 +4,20 @@ module IssuableStates
extend ActiveSupport::Concern
# The state:string column is being migrated to state_id:integer column
- # This is a temporary hook to populate state_id column with new values
- # and should be removed after the state column is removed.
- # Check https://gitlab.com/gitlab-org/gitlab-foss/issues/51789 for more information
+ # This is a temporary hook to keep state column in sync until it is removed.
+ # Check https: https://gitlab.com/gitlab-org/gitlab/issues/33814 for more information
+ # The state column can be safely removed after 2019-10-27
included do
- before_save :set_state_id
+ before_save :sync_issuable_deprecated_state
end
- def set_state_id
- return if state.nil? || state.empty?
+ def sync_issuable_deprecated_state
+ return if self.is_a?(Epic)
+ return unless respond_to?(:state)
+ return if state_id.nil?
- # Needed to prevent breaking some migration specs that
- # rollback database to a point where state_id does not exist.
- # We can use this guard clause for now since this file will
- # be removed in the next release.
- return unless self.has_attribute?(:state_id)
+ deprecated_state = self.class.available_states.key(state_id)
- self.state_id = self.class.available_states[state]
+ self.write_attribute(:state, deprecated_state)
end
end
diff --git a/app/models/concerns/milestoneish.rb b/app/models/concerns/milestoneish.rb
index 3deb86da6cf..42b370990ac 100644
--- a/app/models/concerns/milestoneish.rb
+++ b/app/models/concerns/milestoneish.rb
@@ -6,7 +6,9 @@ module Milestoneish
end
def closed_issues_count(user)
- count_issues_by_state(user)['closed'].to_i
+ closed_state_id = Issue.available_states[:closed]
+
+ count_issues_by_state(user)[closed_state_id].to_i
end
def complete?(user)
@@ -117,7 +119,7 @@ module Milestoneish
def count_issues_by_state(user)
memoize_per_user(user, :count_issues_by_state) do
- issues_visible_to_user(user).reorder(nil).group(:state).count
+ issues_visible_to_user(user).reorder(nil).group(:state_id).count
end
end
diff --git a/app/models/concerns/versioned_description.rb b/app/models/concerns/versioned_description.rb
new file mode 100644
index 00000000000..63a24aadc8a
--- /dev/null
+++ b/app/models/concerns/versioned_description.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module VersionedDescription
+ extend ActiveSupport::Concern
+
+ included do
+ attr_accessor :saved_description_version
+
+ has_many :description_versions
+
+ after_update :save_description_version
+ end
+
+ private
+
+ def save_description_version
+ self.saved_description_version = nil
+
+ return unless Feature.enabled?(:save_description_versions, issuing_parent)
+ return unless saved_change_to_description?
+
+ unless description_versions.exists?
+ description_versions.create!(
+ description: description_before_last_save,
+ created_at: created_at
+ )
+ end
+
+ self.saved_description_version = description_versions.create!(description: description)
+ end
+end
diff --git a/app/models/concerns/worker_attributes.rb b/app/models/concerns/worker_attributes.rb
new file mode 100644
index 00000000000..af40e9e3b19
--- /dev/null
+++ b/app/models/concerns/worker_attributes.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module WorkerAttributes
+ extend ActiveSupport::Concern
+
+ class_methods do
+ def feature_category(value)
+ raise "Invalid category. Use `feature_category_not_owned!` to mark a worker as not owned" if value == :not_owned
+
+ worker_attributes[:feature_category] = value
+ end
+
+ # Special case: mark this work as not associated with a feature category
+ # this should be used for cross-cutting concerns, such as mailer workers.
+ def feature_category_not_owned!
+ worker_attributes[:feature_category] = :not_owned
+ end
+
+ def get_feature_category
+ get_worker_attribute(:feature_category)
+ end
+
+ def feature_category_not_owned?
+ get_worker_attribute(:feature_category) == :not_owned
+ end
+
+ protected
+
+ # Returns a worker attribute declared on this class or its parent class.
+ # This approach allows declared attributes to be inherited by
+ # child classes.
+ def get_worker_attribute(name)
+ worker_attributes[name] || superclass_worker_attributes(name)
+ end
+
+ private
+
+ def worker_attributes
+ @attributes ||= {}
+ end
+
+ def superclass_worker_attributes(name)
+ return unless superclass.include? WorkerAttributes
+
+ superclass.get_worker_attribute(name)
+ end
+ end
+end
diff --git a/app/models/description_version.rb b/app/models/description_version.rb
new file mode 100644
index 00000000000..abab7f94212
--- /dev/null
+++ b/app/models/description_version.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class DescriptionVersion < ApplicationRecord
+ belongs_to :issue
+ belongs_to :merge_request
+
+ validate :exactly_one_issuable
+
+ def self.issuable_attrs
+ %i(issue merge_request).freeze
+ end
+
+ private
+
+ def exactly_one_issuable
+ issuable_count = self.class.issuable_attrs.count { |attr| self["#{attr}_id"] }
+
+ errors.add(:base, "Exactly one of #{self.class.issuable_attrs.join(', ')} is required") if issuable_count != 1
+ end
+end
+
+DescriptionVersion.prepend_if_ee('EE::DescriptionVersion')
diff --git a/app/models/issue.rb b/app/models/issue.rb
index d0b2165fcc7..b9b481ac29b 100644
--- a/app/models/issue.rb
+++ b/app/models/issue.rb
@@ -71,7 +71,7 @@ class Issue < ApplicationRecord
attr_spammable :title, spam_title: true
attr_spammable :description, spam_description: true
- state_machine :state, initial: :opened do
+ state_machine :state_id, initial: :opened do
event :close do
transition [:opened] => :closed
end
@@ -80,8 +80,8 @@ class Issue < ApplicationRecord
transition closed: :opened
end
- state :opened
- state :closed
+ state :opened, value: Issue.available_states[:opened]
+ state :closed, value: Issue.available_states[:closed]
before_transition any => :closed do |issue|
issue.closed_at = issue.system_note_timestamp
@@ -93,6 +93,13 @@ class Issue < ApplicationRecord
end
end
+ # Alias to state machine .with_state_id method
+ # This needs to be defined after the state machine block to avoid errors
+ class << self
+ alias_method :with_state, :with_state_id
+ alias_method :with_states, :with_state_ids
+ end
+
def self.relative_positioning_query_base(issue)
in_projects(issue.parent_ids)
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 50efe5d6aa0..7cdaa3e3ca7 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -85,7 +85,13 @@ class MergeRequest < ApplicationRecord
# when creating new merge request
attr_accessor :can_be_created, :compare_commits, :diff_options, :compare
- state_machine :state, initial: :opened do
+ # Keep states definition to be evaluated before the state_machine block to avoid spec failures.
+ # If this gets evaluated after, the `merged` and `locked` states which are overrided can be nil.
+ def self.available_state_names
+ super + [:merged, :locked]
+ end
+
+ state_machine :state_id, initial: :opened do
event :close do
transition [:opened] => :closed
end
@@ -116,10 +122,17 @@ class MergeRequest < ApplicationRecord
end
end
- state :opened
- state :closed
- state :merged
- state :locked
+ state :opened, value: MergeRequest.available_states[:opened]
+ state :closed, value: MergeRequest.available_states[:closed]
+ state :merged, value: MergeRequest.available_states[:merged]
+ state :locked, value: MergeRequest.available_states[:locked]
+ end
+
+ # Alias to state machine .with_state_id method
+ # This needs to be defined after the state machine block to avoid errors
+ class << self
+ alias_method :with_state, :with_state_id
+ alias_method :with_states, :with_state_ids
end
state_machine :merge_status, initial: :unchecked do
@@ -211,10 +224,6 @@ class MergeRequest < ApplicationRecord
'!'
end
- def self.available_states
- @available_states ||= super.merge(merged: 3, locked: 4)
- end
-
# Returns the top 100 target branches
#
# The returned value is a Array containing branch names
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index fe8ba9765b7..735ad046f22 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -83,7 +83,7 @@ class MergeRequestDiff < ApplicationRecord
metrics_join = mr_diffs.join(mr_metrics).on(metrics_join_condition)
- condition = MergeRequest.arel_table[:state].eq(:merged)
+ condition = MergeRequest.arel_table[:state_id].eq(MergeRequest.available_states[:merged])
.and(MergeRequest::Metrics.arel_table[:merged_at].lteq(before))
.and(MergeRequest::Metrics.arel_table[:merged_at].not_eq(nil))
@@ -91,7 +91,7 @@ class MergeRequestDiff < ApplicationRecord
end
scope :old_closed_diffs, -> (before) do
- condition = MergeRequest.arel_table[:state].eq(:closed)
+ condition = MergeRequest.arel_table[:state_id].eq(MergeRequest.available_states[:closed])
.and(MergeRequest::Metrics.arel_table[:latest_closed_at].lteq(before))
joins(merge_request: :metrics).where(condition)
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 1d0b37abf72..019bd54f48c 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -161,7 +161,7 @@ class HipchatService < Service
obj_attr = data[:object_attributes]
obj_attr = HashWithIndifferentAccess.new(obj_attr)
title = render_line(obj_attr[:title])
- state = obj_attr[:state]
+ state = Issue.available_states.key(obj_attr[:state_id])
issue_iid = obj_attr[:iid]
issue_url = obj_attr[:url]
description = obj_attr[:description]
diff --git a/app/models/push_event.rb b/app/models/push_event.rb
index 6f7365a2763..5cab686f20b 100644
--- a/app/models/push_event.rb
+++ b/app/models/push_event.rb
@@ -54,7 +54,7 @@ class PushEvent < Event
.select(1)
.where('merge_requests.source_project_id = events.project_id')
.where('merge_requests.source_branch = push_event_payloads.ref')
- .where(state: :opened)
+ .with_state(:opened)
# For reasons unknown the use of #eager_load will result in the
# "push_event_payload" association not being set. Because of this we're
diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb
index 8ec90ca25d3..11cbeb60bba 100644
--- a/app/models/system_note_metadata.rb
+++ b/app/models/system_note_metadata.rb
@@ -23,6 +23,7 @@ class SystemNoteMetadata < ApplicationRecord
validates :action, inclusion: { in: :icon_types }, allow_nil: true
belongs_to :note
+ belongs_to :description_version
def icon_types
ICON_TYPES
diff --git a/app/serializers/projects/serverless/service_entity.rb b/app/serializers/projects/serverless/service_entity.rb
index 40ac52d96af..a1e0bf02d11 100644
--- a/app/serializers/projects/serverless/service_entity.rb
+++ b/app/serializers/projects/serverless/service_entity.rb
@@ -44,7 +44,7 @@ module Projects
end
expose :url do |service|
- service.dig('status', 'url')
+ service.dig('status', 'url') || "http://#{service.dig('status', 'domain')}"
end
expose :description do |service|
diff --git a/app/services/issuable/clone/content_rewriter.rb b/app/services/issuable/clone/content_rewriter.rb
index f75b51c4be3..67d2f9fd3fe 100644
--- a/app/services/issuable/clone/content_rewriter.rb
+++ b/app/services/issuable/clone/content_rewriter.rb
@@ -39,6 +39,10 @@ module Issuable
if note.system_note_metadata
new_params[:system_note_metadata] = note.system_note_metadata.dup
+
+ # TODO: Implement copying of description versions when an issue is moved
+ # https://gitlab.com/gitlab-org/gitlab/issues/32300
+ new_params[:system_note_metadata].description_version = nil
end
new_note.update(new_params)
diff --git a/app/services/note_summary.rb b/app/services/note_summary.rb
index 60a68568833..6fe14939aaa 100644
--- a/app/services/note_summary.rb
+++ b/app/services/note_summary.rb
@@ -10,6 +10,10 @@ class NoteSummary
project: project, author: author, note: body }
@metadata = { action: action, commit_count: commit_count }.compact
+ if action == 'description' && noteable.saved_description_version
+ @metadata[:description_version] = noteable.saved_description_version
+ end
+
set_commit_params if note[:noteable].is_a?(Commit)
end
diff --git a/app/uploaders/object_storage.rb b/app/uploaders/object_storage.rb
index f99ad987156..36bde629f9c 100644
--- a/app/uploaders/object_storage.rb
+++ b/app/uploaders/object_storage.rb
@@ -180,10 +180,11 @@ module ObjectStorage
end
def workhorse_authorize(has_length:, maximum_size: nil)
- {
- RemoteObject: workhorse_remote_upload_options(has_length: has_length, maximum_size: maximum_size),
- TempPath: workhorse_local_upload_path
- }.compact
+ if self.object_store_enabled? && self.direct_upload_enabled?
+ { RemoteObject: workhorse_remote_upload_options(has_length: has_length, maximum_size: maximum_size) }
+ else
+ { TempPath: workhorse_local_upload_path }
+ end
end
def workhorse_local_upload_path
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 49e482ff1df..2633a3899f7 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -20,4 +20,5 @@
- if new_issue_email
= render 'projects/issuable_by_email', email: new_issue_email, issuable_type: 'issue'
- else
- = render 'shared/empty_states/issues', button_path: new_project_issue_path(@project), show_import_button: true
+ - new_project_issue_button_path = @project.archived? ? false : new_project_issue_path(@project)
+ = render 'shared/empty_states/issues', new_project_issue_button_path: new_project_issue_button_path, show_import_button: true
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 9173b802dd4..325e01bb5c8 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -1,4 +1,4 @@
-- button_path = local_assigns.fetch(:button_path, false)
+- button_path = local_assigns.fetch(:new_project_issue_button_path, false)
- project_select_button = local_assigns.fetch(:project_select_button, false)
- show_import_button = local_assigns.fetch(:show_import_button, false) && can?(current_user, :import_issues, @project)
- has_button = button_path || project_select_button
@@ -56,4 +56,3 @@
- if show_import_button
= render 'projects/issues/import_csv/modal'
-
diff --git a/app/workers/admin_email_worker.rb b/app/workers/admin_email_worker.rb
index f69e74b2674..be05d2a6752 100644
--- a/app/workers/admin_email_worker.rb
+++ b/app/workers/admin_email_worker.rb
@@ -4,6 +4,8 @@ class AdminEmailWorker
include ApplicationWorker
include CronjobQueue
+ feature_category_not_owned!
+
def perform
send_repository_check_mail if Gitlab::CurrentSettings.repository_checks_enabled
end
diff --git a/app/workers/authorized_projects_worker.rb b/app/workers/authorized_projects_worker.rb
index c9ddeb08613..577c439f4a2 100644
--- a/app/workers/authorized_projects_worker.rb
+++ b/app/workers/authorized_projects_worker.rb
@@ -4,6 +4,8 @@ class AuthorizedProjectsWorker
include ApplicationWorker
prepend WaitableWorker
+ feature_category :authentication_and_authorization
+
# This is a workaround for a Ruby 2.3.7 bug. rspec-mocks cannot restore the
# visibility of prepended modules. See https://github.com/rspec/rspec-mocks/issues/1231
# for more details.
diff --git a/app/workers/auto_merge_process_worker.rb b/app/workers/auto_merge_process_worker.rb
index cd81cdbc60c..e4dccb891ce 100644
--- a/app/workers/auto_merge_process_worker.rb
+++ b/app/workers/auto_merge_process_worker.rb
@@ -4,6 +4,7 @@ class AutoMergeProcessWorker
include ApplicationWorker
queue_namespace :auto_merge
+ feature_category :continuous_delivery
def perform(merge_request_id)
MergeRequest.find_by_id(merge_request_id).try do |merge_request|
diff --git a/app/workers/background_migration_worker.rb b/app/workers/background_migration_worker.rb
index b83412b5e6e..20e2cdd7f96 100644
--- a/app/workers/background_migration_worker.rb
+++ b/app/workers/background_migration_worker.rb
@@ -3,6 +3,8 @@
class BackgroundMigrationWorker
include ApplicationWorker
+ feature_category_not_owned!
+
# The minimum amount of time between processing two jobs of the same migration
# class.
#
diff --git a/app/workers/build_hooks_worker.rb b/app/workers/build_hooks_worker.rb
index b0c3676714c..15b31acf3e5 100644
--- a/app/workers/build_hooks_worker.rb
+++ b/app/workers/build_hooks_worker.rb
@@ -5,6 +5,7 @@ class BuildHooksWorker
include PipelineQueue
queue_namespace :pipeline_hooks
+ feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
diff --git a/app/workers/build_queue_worker.rb b/app/workers/build_queue_worker.rb
index 67d5b0f5f5b..6584fba4c65 100644
--- a/app/workers/build_queue_worker.rb
+++ b/app/workers/build_queue_worker.rb
@@ -5,6 +5,7 @@ class BuildQueueWorker
include PipelineQueue
queue_namespace :pipeline_processing
+ feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
diff --git a/app/workers/chat_notification_worker.rb b/app/workers/chat_notification_worker.rb
index 25a306e94d8..3bc2edad62c 100644
--- a/app/workers/chat_notification_worker.rb
+++ b/app/workers/chat_notification_worker.rb
@@ -3,6 +3,8 @@
class ChatNotificationWorker
include ApplicationWorker
+ feature_category :chatops
+
RESCHEDULE_INTERVAL = 2.seconds
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/workers/ci/archive_traces_cron_worker.rb b/app/workers/ci/archive_traces_cron_worker.rb
index ad7a29719ac..74f389175b9 100644
--- a/app/workers/ci/archive_traces_cron_worker.rb
+++ b/app/workers/ci/archive_traces_cron_worker.rb
@@ -5,6 +5,8 @@ module Ci
include ApplicationWorker
include CronjobQueue
+ feature_category :continuous_integration
+
# rubocop: disable CodeReuse/ActiveRecord
def perform
# Archive stale live traces which still resides in redis or database
diff --git a/app/workers/ci/build_prepare_worker.rb b/app/workers/ci/build_prepare_worker.rb
index 1a35a74ae53..20208c18d03 100644
--- a/app/workers/ci/build_prepare_worker.rb
+++ b/app/workers/ci/build_prepare_worker.rb
@@ -6,6 +6,7 @@ module Ci
include PipelineQueue
queue_namespace :pipeline_processing
+ feature_category :continuous_integration
def perform(build_id)
Ci::Build.find_by_id(build_id).try do |build|
diff --git a/app/workers/ci/build_schedule_worker.rb b/app/workers/ci/build_schedule_worker.rb
index da219adffc6..f22ec4c7810 100644
--- a/app/workers/ci/build_schedule_worker.rb
+++ b/app/workers/ci/build_schedule_worker.rb
@@ -6,6 +6,7 @@ module Ci
include PipelineQueue
queue_namespace :pipeline_processing
+ feature_category :continuous_integration
def perform(build_id)
::Ci::Build.find_by_id(build_id).try do |build|
diff --git a/app/workers/cleanup_container_repository_worker.rb b/app/workers/cleanup_container_repository_worker.rb
index 0331fc7b01c..83fb3e58d29 100644
--- a/app/workers/cleanup_container_repository_worker.rb
+++ b/app/workers/cleanup_container_repository_worker.rb
@@ -4,6 +4,7 @@ class CleanupContainerRepositoryWorker
include ApplicationWorker
queue_namespace :container_repository
+ feature_category :container_registry
attr_reader :container_repository, :current_user
diff --git a/app/workers/concerns/application_worker.rb b/app/workers/concerns/application_worker.rb
index 2b36ccb8304..62748808ff1 100644
--- a/app/workers/concerns/application_worker.rb
+++ b/app/workers/concerns/application_worker.rb
@@ -8,6 +8,7 @@ module ApplicationWorker
extend ActiveSupport::Concern
include Sidekiq::Worker # rubocop:disable Cop/IncludeSidekiqWorker
+ include WorkerAttributes
included do
set_queue
diff --git a/app/workers/concerns/auto_devops_queue.rb b/app/workers/concerns/auto_devops_queue.rb
index aba928ccaab..61e3c1544bd 100644
--- a/app/workers/concerns/auto_devops_queue.rb
+++ b/app/workers/concerns/auto_devops_queue.rb
@@ -5,5 +5,6 @@ module AutoDevopsQueue
included do
queue_namespace :auto_devops
+ feature_category :auto_devops
end
end
diff --git a/app/workers/concerns/chaos_queue.rb b/app/workers/concerns/chaos_queue.rb
index e406509d12d..c5db10491f2 100644
--- a/app/workers/concerns/chaos_queue.rb
+++ b/app/workers/concerns/chaos_queue.rb
@@ -5,5 +5,6 @@ module ChaosQueue
included do
queue_namespace :chaos
+ feature_category :chaos_engineering
end
end
diff --git a/app/workers/concerns/cluster_queue.rb b/app/workers/concerns/cluster_queue.rb
index e44b40c36c9..180b86b0124 100644
--- a/app/workers/concerns/cluster_queue.rb
+++ b/app/workers/concerns/cluster_queue.rb
@@ -8,5 +8,6 @@ module ClusterQueue
included do
queue_namespace :gcp_cluster
+ feature_category :kubernetes_configuration
end
end
diff --git a/app/workers/concerns/gitlab/github_import/object_importer.rb b/app/workers/concerns/gitlab/github_import/object_importer.rb
index eeeff6e93a0..b856a9329dd 100644
--- a/app/workers/concerns/gitlab/github_import/object_importer.rb
+++ b/app/workers/concerns/gitlab/github_import/object_importer.rb
@@ -12,6 +12,8 @@ module Gitlab
include GithubImport::Queue
include ReschedulingMethods
include NotifyUponDeath
+
+ feature_category :importers
end
# project - An instance of `Project` to import the data into.
diff --git a/app/workers/concerns/gitlab/github_import/queue.rb b/app/workers/concerns/gitlab/github_import/queue.rb
index 59b621f16ab..7cc23dd7c0b 100644
--- a/app/workers/concerns/gitlab/github_import/queue.rb
+++ b/app/workers/concerns/gitlab/github_import/queue.rb
@@ -7,6 +7,7 @@ module Gitlab
included do
queue_namespace :github_importer
+ feature_category :importers
# If a job produces an error it may block a stage from advancing
# forever. To prevent this from happening we prevent jobs from going to
diff --git a/app/workers/concerns/object_pool_queue.rb b/app/workers/concerns/object_pool_queue.rb
index 5b648df9c72..c2e84470fba 100644
--- a/app/workers/concerns/object_pool_queue.rb
+++ b/app/workers/concerns/object_pool_queue.rb
@@ -8,5 +8,6 @@ module ObjectPoolQueue
included do
queue_namespace :object_pool
+ feature_category :gitaly
end
end
diff --git a/app/workers/concerns/pipeline_background_queue.rb b/app/workers/concerns/pipeline_background_queue.rb
index bbb8ad0c982..0a23780b807 100644
--- a/app/workers/concerns/pipeline_background_queue.rb
+++ b/app/workers/concerns/pipeline_background_queue.rb
@@ -8,5 +8,6 @@ module PipelineBackgroundQueue
included do
queue_namespace :pipeline_background
+ feature_category :continuous_integration
end
end
diff --git a/app/workers/concerns/pipeline_queue.rb b/app/workers/concerns/pipeline_queue.rb
index 3aaed4669e5..27cbf6eb61c 100644
--- a/app/workers/concerns/pipeline_queue.rb
+++ b/app/workers/concerns/pipeline_queue.rb
@@ -8,5 +8,6 @@ module PipelineQueue
included do
queue_namespace :pipeline_default
+ feature_category :continuous_integration
end
end
diff --git a/app/workers/concerns/repository_check_queue.rb b/app/workers/concerns/repository_check_queue.rb
index 216d67e5dbc..76f6e1c2e91 100644
--- a/app/workers/concerns/repository_check_queue.rb
+++ b/app/workers/concerns/repository_check_queue.rb
@@ -6,7 +6,7 @@ module RepositoryCheckQueue
included do
queue_namespace :repository_check
-
sidekiq_options retry: false
+ feature_category :source_code_management
end
end
diff --git a/app/workers/concerns/todos_destroyer_queue.rb b/app/workers/concerns/todos_destroyer_queue.rb
index 8e2b1d30579..1bbccbfb1f9 100644
--- a/app/workers/concerns/todos_destroyer_queue.rb
+++ b/app/workers/concerns/todos_destroyer_queue.rb
@@ -8,5 +8,6 @@ module TodosDestroyerQueue
included do
queue_namespace :todos_destroyer
+ feature_category :issue_tracking
end
end
diff --git a/app/workers/create_evidence_worker.rb b/app/workers/create_evidence_worker.rb
index 5fc901ae514..027dbd2f101 100644
--- a/app/workers/create_evidence_worker.rb
+++ b/app/workers/create_evidence_worker.rb
@@ -3,6 +3,8 @@
class CreateEvidenceWorker
include ApplicationWorker
+ feature_category :release_governance
+
def perform(release_id)
release = Release.find_by_id(release_id)
return unless release
diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb
index e3fb5d479ae..fc36a2adccd 100644
--- a/app/workers/create_gpg_signature_worker.rb
+++ b/app/workers/create_gpg_signature_worker.rb
@@ -3,6 +3,8 @@
class CreateGpgSignatureWorker
include ApplicationWorker
+ feature_category :source_code_management
+
# rubocop: disable CodeReuse/ActiveRecord
def perform(commit_shas, project_id)
# Older versions of Git::BranchPushService may push a single commit ID on
diff --git a/app/workers/create_note_diff_file_worker.rb b/app/workers/create_note_diff_file_worker.rb
index 0850250f7e3..ca200bd17b4 100644
--- a/app/workers/create_note_diff_file_worker.rb
+++ b/app/workers/create_note_diff_file_worker.rb
@@ -3,6 +3,8 @@
class CreateNoteDiffFileWorker
include ApplicationWorker
+ feature_category :source_code_management
+
def perform(diff_note_id)
diff_note = DiffNote.find(diff_note_id)
diff --git a/app/workers/create_pipeline_worker.rb b/app/workers/create_pipeline_worker.rb
index 037b4a57d4b..70412ffd095 100644
--- a/app/workers/create_pipeline_worker.rb
+++ b/app/workers/create_pipeline_worker.rb
@@ -5,6 +5,7 @@ class CreatePipelineWorker
include PipelineQueue
queue_namespace :pipeline_creation
+ feature_category :continuous_integration
def perform(project_id, user_id, ref, source, params = {})
project = Project.find(project_id)
diff --git a/app/workers/delete_container_repository_worker.rb b/app/workers/delete_container_repository_worker.rb
index 42e66513ff1..e70b4fb0a58 100644
--- a/app/workers/delete_container_repository_worker.rb
+++ b/app/workers/delete_container_repository_worker.rb
@@ -5,6 +5,7 @@ class DeleteContainerRepositoryWorker
include ExclusiveLeaseGuard
queue_namespace :container_repository
+ feature_category :container_registry
LEASE_TIMEOUT = 1.hour
diff --git a/app/workers/delete_diff_files_worker.rb b/app/workers/delete_diff_files_worker.rb
index f518dfe871c..e0c1724f1f7 100644
--- a/app/workers/delete_diff_files_worker.rb
+++ b/app/workers/delete_diff_files_worker.rb
@@ -3,6 +3,8 @@
class DeleteDiffFilesWorker
include ApplicationWorker
+ feature_category :source_code_management
+
# rubocop: disable CodeReuse/ActiveRecord
def perform(merge_request_diff_id)
merge_request_diff = MergeRequestDiff.find(merge_request_diff_id)
diff --git a/app/workers/delete_merged_branches_worker.rb b/app/workers/delete_merged_branches_worker.rb
index 017d7fd1cb0..44b3db30d0d 100644
--- a/app/workers/delete_merged_branches_worker.rb
+++ b/app/workers/delete_merged_branches_worker.rb
@@ -3,6 +3,8 @@
class DeleteMergedBranchesWorker
include ApplicationWorker
+ feature_category :source_code_management
+
def perform(project_id, user_id)
begin
project = Project.find(project_id)
diff --git a/app/workers/delete_stored_files_worker.rb b/app/workers/delete_stored_files_worker.rb
index ff7931849d8..8a693a64055 100644
--- a/app/workers/delete_stored_files_worker.rb
+++ b/app/workers/delete_stored_files_worker.rb
@@ -3,6 +3,8 @@
class DeleteStoredFilesWorker
include ApplicationWorker
+ feature_category_not_owned!
+
def perform(class_name, keys)
klass = begin
class_name.constantize
diff --git a/app/workers/delete_user_worker.rb b/app/workers/delete_user_worker.rb
index efa8794b214..0e49e787d8a 100644
--- a/app/workers/delete_user_worker.rb
+++ b/app/workers/delete_user_worker.rb
@@ -3,6 +3,8 @@
class DeleteUserWorker
include ApplicationWorker
+ feature_category :authentication_and_authorization
+
def perform(current_user_id, delete_user_id, options = {})
delete_user = User.find(delete_user_id)
current_user = User.find(current_user_id)
diff --git a/app/workers/deployments/finished_worker.rb b/app/workers/deployments/finished_worker.rb
index c9d448d5d18..79a1caccc92 100644
--- a/app/workers/deployments/finished_worker.rb
+++ b/app/workers/deployments/finished_worker.rb
@@ -5,6 +5,7 @@ module Deployments
include ApplicationWorker
queue_namespace :deployment
+ feature_category :continuous_delivery
def perform(deployment_id)
Deployment.find_by_id(deployment_id).try(:execute_hooks)
diff --git a/app/workers/deployments/success_worker.rb b/app/workers/deployments/success_worker.rb
index 3c7e384365a..f6520307186 100644
--- a/app/workers/deployments/success_worker.rb
+++ b/app/workers/deployments/success_worker.rb
@@ -5,6 +5,7 @@ module Deployments
include ApplicationWorker
queue_namespace :deployment
+ feature_category :continuous_delivery
def perform(deployment_id)
Deployment.find_by_id(deployment_id).try do |deployment|
diff --git a/app/workers/detect_repository_languages_worker.rb b/app/workers/detect_repository_languages_worker.rb
index 838c3be78f0..954d0f9336b 100644
--- a/app/workers/detect_repository_languages_worker.rb
+++ b/app/workers/detect_repository_languages_worker.rb
@@ -6,6 +6,7 @@ class DetectRepositoryLanguagesWorker
include ExclusiveLeaseGuard
sidekiq_options retry: 1
+ feature_category :source_code_management
LEASE_TIMEOUT = 300
diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb
index e70bf17d5a9..c82728be329 100644
--- a/app/workers/email_receiver_worker.rb
+++ b/app/workers/email_receiver_worker.rb
@@ -3,6 +3,8 @@
class EmailReceiverWorker
include ApplicationWorker
+ feature_category :issue_tracking
+
def perform(raw)
return unless Gitlab::IncomingEmail.enabled?
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index ed3e354e4c2..2231c91a720 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -5,6 +5,8 @@ class EmailsOnPushWorker
attr_reader :email, :skip_premailer
+ feature_category :source_code_management
+
def perform(project_id, recipients, push_data, options = {})
options.symbolize_keys!
options.reverse_merge!(
diff --git a/app/workers/expire_build_artifacts_worker.rb b/app/workers/expire_build_artifacts_worker.rb
index 6f0e0fd33f7..9545227fa31 100644
--- a/app/workers/expire_build_artifacts_worker.rb
+++ b/app/workers/expire_build_artifacts_worker.rb
@@ -4,6 +4,8 @@ class ExpireBuildArtifactsWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :continuous_integration
+
def perform
if Feature.enabled?(:ci_new_expire_job_artifacts_service, default_enabled: true)
perform_efficient_artifacts_removal
diff --git a/app/workers/expire_build_instance_artifacts_worker.rb b/app/workers/expire_build_instance_artifacts_worker.rb
index 71e61dcb878..db5240d5c8e 100644
--- a/app/workers/expire_build_instance_artifacts_worker.rb
+++ b/app/workers/expire_build_instance_artifacts_worker.rb
@@ -3,6 +3,8 @@
class ExpireBuildInstanceArtifactsWorker
include ApplicationWorker
+ feature_category :continuous_integration
+
# rubocop: disable CodeReuse/ActiveRecord
def perform(build_id)
build = Ci::Build
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
index 5499e12e49b..ad119917774 100644
--- a/app/workers/git_garbage_collect_worker.rb
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -4,6 +4,7 @@ class GitGarbageCollectWorker
include ApplicationWorker
sidekiq_options retry: false
+ feature_category :gitaly
# Timeout set to 24h
LEASE_TIMEOUT = 86400
diff --git a/app/workers/gitlab/github_import/advance_stage_worker.rb b/app/workers/gitlab/github_import/advance_stage_worker.rb
index 0b3437a8a33..44e69e48694 100644
--- a/app/workers/gitlab/github_import/advance_stage_worker.rb
+++ b/app/workers/gitlab/github_import/advance_stage_worker.rb
@@ -10,6 +10,7 @@ module Gitlab
include ApplicationWorker
sidekiq_options dead: false
+ feature_category :importers
INTERVAL = 30.seconds.to_i
diff --git a/app/workers/gitlab_shell_worker.rb b/app/workers/gitlab_shell_worker.rb
index 0e4d40acc5c..9766331cf4b 100644
--- a/app/workers/gitlab_shell_worker.rb
+++ b/app/workers/gitlab_shell_worker.rb
@@ -4,6 +4,8 @@ class GitlabShellWorker
include ApplicationWorker
include Gitlab::ShellAdapter
+ feature_category :source_code_management
+
def perform(action, *arg)
gitlab_shell.__send__(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
end
diff --git a/app/workers/gitlab_usage_ping_worker.rb b/app/workers/gitlab_usage_ping_worker.rb
index a5e22f88a3b..ad8302a844a 100644
--- a/app/workers/gitlab_usage_ping_worker.rb
+++ b/app/workers/gitlab_usage_ping_worker.rb
@@ -6,6 +6,8 @@ class GitlabUsagePingWorker
include ApplicationWorker
include CronjobQueue
+ feature_category_not_owned!
+
# Retry for up to approximately three hours then give up.
sidekiq_options retry: 10, dead: false
diff --git a/app/workers/group_destroy_worker.rb b/app/workers/group_destroy_worker.rb
index b4a3ddcae51..553fd359baf 100644
--- a/app/workers/group_destroy_worker.rb
+++ b/app/workers/group_destroy_worker.rb
@@ -4,6 +4,8 @@ class GroupDestroyWorker
include ApplicationWorker
include ExceptionBacktrace
+ feature_category :groups
+
def perform(group_id, user_id)
begin
group = Group.find(group_id)
diff --git a/app/workers/hashed_storage/base_worker.rb b/app/workers/hashed_storage/base_worker.rb
index 237e278c537..1ab2108f6bb 100644
--- a/app/workers/hashed_storage/base_worker.rb
+++ b/app/workers/hashed_storage/base_worker.rb
@@ -3,6 +3,9 @@
module HashedStorage
class BaseWorker
include ExclusiveLeaseGuard
+ include WorkerAttributes
+
+ feature_category :source_code_management
LEASE_TIMEOUT = 30.seconds.to_i
LEASE_KEY_SEGMENT = 'project_migrate_hashed_storage_worker'
diff --git a/app/workers/hashed_storage/migrator_worker.rb b/app/workers/hashed_storage/migrator_worker.rb
index 49e347d4060..72a3faec5f4 100644
--- a/app/workers/hashed_storage/migrator_worker.rb
+++ b/app/workers/hashed_storage/migrator_worker.rb
@@ -5,6 +5,7 @@ module HashedStorage
include ApplicationWorker
queue_namespace :hashed_storage
+ feature_category :source_code_management
# @param [Integer] start initial ID of the batch
# @param [Integer] finish last ID of the batch
diff --git a/app/workers/hashed_storage/rollbacker_worker.rb b/app/workers/hashed_storage/rollbacker_worker.rb
index a4da8443787..8babdcfb96d 100644
--- a/app/workers/hashed_storage/rollbacker_worker.rb
+++ b/app/workers/hashed_storage/rollbacker_worker.rb
@@ -5,6 +5,7 @@ module HashedStorage
include ApplicationWorker
queue_namespace :hashed_storage
+ feature_category :source_code_management
# @param [Integer] start initial ID of the batch
# @param [Integer] finish last ID of the batch
diff --git a/app/workers/import_export_project_cleanup_worker.rb b/app/workers/import_export_project_cleanup_worker.rb
index da3debdeede..07c29d40b54 100644
--- a/app/workers/import_export_project_cleanup_worker.rb
+++ b/app/workers/import_export_project_cleanup_worker.rb
@@ -4,6 +4,8 @@ class ImportExportProjectCleanupWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :importers
+
def perform
ImportExportCleanUpService.new.execute
end
diff --git a/app/workers/import_issues_csv_worker.rb b/app/workers/import_issues_csv_worker.rb
index b9d7099af71..d9834320318 100644
--- a/app/workers/import_issues_csv_worker.rb
+++ b/app/workers/import_issues_csv_worker.rb
@@ -3,6 +3,8 @@
class ImportIssuesCsvWorker
include ApplicationWorker
+ feature_category :issue_tracking
+
sidekiq_retries_exhausted do |job|
Upload.find(job['args'][2]).destroy
end
diff --git a/app/workers/invalid_gpg_signature_update_worker.rb b/app/workers/invalid_gpg_signature_update_worker.rb
index fc8a731b427..573efdf9fb1 100644
--- a/app/workers/invalid_gpg_signature_update_worker.rb
+++ b/app/workers/invalid_gpg_signature_update_worker.rb
@@ -3,6 +3,8 @@
class InvalidGpgSignatureUpdateWorker
include ApplicationWorker
+ feature_category :source_code_management
+
# rubocop: disable CodeReuse/ActiveRecord
def perform(gpg_key_id)
gpg_key = GpgKey.find_by(id: gpg_key_id)
diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb
index 29631c6b7ac..a133ed6ed1b 100644
--- a/app/workers/irker_worker.rb
+++ b/app/workers/irker_worker.rb
@@ -6,6 +6,8 @@ require 'socket'
class IrkerWorker
include ApplicationWorker
+ feature_category :integrations
+
def perform(project_id, chans, colors, push_data, settings)
project = Project.find(project_id)
diff --git a/app/workers/issue_due_scheduler_worker.rb b/app/workers/issue_due_scheduler_worker.rb
index 476cba47ad7..d4d47659ef0 100644
--- a/app/workers/issue_due_scheduler_worker.rb
+++ b/app/workers/issue_due_scheduler_worker.rb
@@ -4,6 +4,8 @@ class IssueDueSchedulerWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :issue_tracking
+
# rubocop: disable CodeReuse/ActiveRecord
def perform
project_ids = Issue.opened.due_tomorrow.group(:project_id).pluck(:project_id).map { |id| [id] }
diff --git a/app/workers/mail_scheduler/issue_due_worker.rb b/app/workers/mail_scheduler/issue_due_worker.rb
index 1e1dde1e829..6df816de71f 100644
--- a/app/workers/mail_scheduler/issue_due_worker.rb
+++ b/app/workers/mail_scheduler/issue_due_worker.rb
@@ -5,6 +5,8 @@ module MailScheduler
include ApplicationWorker
include MailSchedulerQueue
+ feature_category :issue_tracking
+
# rubocop: disable CodeReuse/ActiveRecord
def perform(project_id)
Issue.opened.due_tomorrow.in_projects(project_id).preload(:project).find_each do |issue|
diff --git a/app/workers/mail_scheduler/notification_service_worker.rb b/app/workers/mail_scheduler/notification_service_worker.rb
index 421fbf04e28..0d06dab3b2e 100644
--- a/app/workers/mail_scheduler/notification_service_worker.rb
+++ b/app/workers/mail_scheduler/notification_service_worker.rb
@@ -7,6 +7,8 @@ module MailScheduler
include ApplicationWorker
include MailSchedulerQueue
+ feature_category :issue_tracking
+
def perform(meth, *args)
check_arguments!(args)
diff --git a/app/workers/merge_worker.rb b/app/workers/merge_worker.rb
index ee864b733cd..70b909afea8 100644
--- a/app/workers/merge_worker.rb
+++ b/app/workers/merge_worker.rb
@@ -3,6 +3,8 @@
class MergeWorker
include ApplicationWorker
+ feature_category :source_code_management
+
def perform(merge_request_id, current_user_id, params)
params = params.with_indifferent_access
current_user = User.find(current_user_id)
diff --git a/app/workers/migrate_external_diffs_worker.rb b/app/workers/migrate_external_diffs_worker.rb
index fe757968d49..d248e2b5500 100644
--- a/app/workers/migrate_external_diffs_worker.rb
+++ b/app/workers/migrate_external_diffs_worker.rb
@@ -3,6 +3,8 @@
class MigrateExternalDiffsWorker
include ApplicationWorker
+ feature_category :source_code_management
+
def perform(merge_request_diff_id)
diff = MergeRequestDiff.find_by_id(merge_request_diff_id)
return unless diff
diff --git a/app/workers/namespaceless_project_destroy_worker.rb b/app/workers/namespaceless_project_destroy_worker.rb
index f6e98746055..113afc268f2 100644
--- a/app/workers/namespaceless_project_destroy_worker.rb
+++ b/app/workers/namespaceless_project_destroy_worker.rb
@@ -10,6 +10,8 @@ class NamespacelessProjectDestroyWorker
include ApplicationWorker
include ExceptionBacktrace
+ feature_category :authentication_and_authorization
+
def perform(project_id)
begin
project = Project.unscoped.find(project_id)
@@ -31,6 +33,6 @@ class NamespacelessProjectDestroyWorker
def unlink_fork(project)
merge_requests = project.forked_from_project.merge_requests.opened.from_project(project)
- merge_requests.update_all(state: 'closed')
+ merge_requests.update_all(state_id: MergeRequest.available_states[:closed])
end
end
diff --git a/app/workers/namespaces/prune_aggregation_schedules_worker.rb b/app/workers/namespaces/prune_aggregation_schedules_worker.rb
index 4e40feee702..16259ffbfa6 100644
--- a/app/workers/namespaces/prune_aggregation_schedules_worker.rb
+++ b/app/workers/namespaces/prune_aggregation_schedules_worker.rb
@@ -5,6 +5,8 @@ module Namespaces
include ApplicationWorker
include CronjobQueue
+ feature_category :source_code_management
+
# Worker to prune pending rows on Namespace::AggregationSchedule
# It's scheduled to run once a day at 1:05am.
def perform
diff --git a/app/workers/namespaces/root_statistics_worker.rb b/app/workers/namespaces/root_statistics_worker.rb
index 0c1ca5eb975..fd772c8cff6 100644
--- a/app/workers/namespaces/root_statistics_worker.rb
+++ b/app/workers/namespaces/root_statistics_worker.rb
@@ -5,6 +5,7 @@ module Namespaces
include ApplicationWorker
queue_namespace :update_namespace_statistics
+ feature_category :source_code_management
def perform(namespace_id)
namespace = Namespace.find(namespace_id)
diff --git a/app/workers/namespaces/schedule_aggregation_worker.rb b/app/workers/namespaces/schedule_aggregation_worker.rb
index b7d580220d6..87e135fbf21 100644
--- a/app/workers/namespaces/schedule_aggregation_worker.rb
+++ b/app/workers/namespaces/schedule_aggregation_worker.rb
@@ -5,6 +5,7 @@ module Namespaces
include ApplicationWorker
queue_namespace :update_namespace_statistics
+ feature_category :source_code_management
def perform(namespace_id)
return unless aggregation_schedules_table_exists?
diff --git a/app/workers/new_issue_worker.rb b/app/workers/new_issue_worker.rb
index 85b53973f56..1b0fec597e7 100644
--- a/app/workers/new_issue_worker.rb
+++ b/app/workers/new_issue_worker.rb
@@ -4,6 +4,8 @@ class NewIssueWorker
include ApplicationWorker
include NewIssuable
+ feature_category :issue_tracking
+
def perform(issue_id, user_id)
return unless objects_found?(issue_id, user_id)
diff --git a/app/workers/new_merge_request_worker.rb b/app/workers/new_merge_request_worker.rb
index fa48c1b29a8..0a5b2f86331 100644
--- a/app/workers/new_merge_request_worker.rb
+++ b/app/workers/new_merge_request_worker.rb
@@ -4,6 +4,8 @@ class NewMergeRequestWorker
include ApplicationWorker
include NewIssuable
+ feature_category :source_code_management
+
def perform(merge_request_id, user_id)
return unless objects_found?(merge_request_id, user_id)
diff --git a/app/workers/new_note_worker.rb b/app/workers/new_note_worker.rb
index 7648af3a8b9..d0d2a563738 100644
--- a/app/workers/new_note_worker.rb
+++ b/app/workers/new_note_worker.rb
@@ -3,6 +3,8 @@
class NewNoteWorker
include ApplicationWorker
+ feature_category :issue_tracking
+
# Keep extra parameter to preserve backwards compatibility with
# old `NewNoteWorker` jobs (can remove later)
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/workers/new_release_worker.rb b/app/workers/new_release_worker.rb
index b80553a60db..28d2517238e 100644
--- a/app/workers/new_release_worker.rb
+++ b/app/workers/new_release_worker.rb
@@ -4,6 +4,7 @@ class NewReleaseWorker
include ApplicationWorker
queue_namespace :notifications
+ feature_category :release_orchestration
def perform(release_id)
release = Release.with_project_and_namespace.find_by_id(release_id)
diff --git a/app/workers/object_storage/background_move_worker.rb b/app/workers/object_storage/background_move_worker.rb
index 19ccae7739c..55f8e1c3ede 100644
--- a/app/workers/object_storage/background_move_worker.rb
+++ b/app/workers/object_storage/background_move_worker.rb
@@ -6,6 +6,7 @@ module ObjectStorage
include ObjectStorageQueue
sidekiq_options retry: 5
+ feature_category_not_owned!
def perform(uploader_class_name, subject_class_name, file_field, subject_id)
uploader_class = uploader_class_name.constantize
diff --git a/app/workers/object_storage/migrate_uploads_worker.rb b/app/workers/object_storage/migrate_uploads_worker.rb
index c9fd19cf9d7..01e6fdb2d3e 100644
--- a/app/workers/object_storage/migrate_uploads_worker.rb
+++ b/app/workers/object_storage/migrate_uploads_worker.rb
@@ -5,6 +5,8 @@ module ObjectStorage
include ApplicationWorker
include ObjectStorageQueue
+ feature_category_not_owned!
+
SanityCheckError = Class.new(StandardError)
class MigrationResult
diff --git a/app/workers/pages_domain_removal_cron_worker.rb b/app/workers/pages_domain_removal_cron_worker.rb
index 79f38e1b89f..25e747c78d0 100644
--- a/app/workers/pages_domain_removal_cron_worker.rb
+++ b/app/workers/pages_domain_removal_cron_worker.rb
@@ -4,6 +4,8 @@ class PagesDomainRemovalCronWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :pages
+
def perform
PagesDomain.for_removal.find_each do |domain|
domain.destroy!
diff --git a/app/workers/pages_domain_ssl_renewal_cron_worker.rb b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
index e5dde07a648..f7a243e9b3b 100644
--- a/app/workers/pages_domain_ssl_renewal_cron_worker.rb
+++ b/app/workers/pages_domain_ssl_renewal_cron_worker.rb
@@ -4,6 +4,8 @@ class PagesDomainSslRenewalCronWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :pages
+
def perform
return unless ::Gitlab::LetsEncrypt.enabled?
diff --git a/app/workers/pages_domain_ssl_renewal_worker.rb b/app/workers/pages_domain_ssl_renewal_worker.rb
index 87fd8059946..4db7d22ef7e 100644
--- a/app/workers/pages_domain_ssl_renewal_worker.rb
+++ b/app/workers/pages_domain_ssl_renewal_worker.rb
@@ -3,6 +3,8 @@
class PagesDomainSslRenewalWorker
include ApplicationWorker
+ feature_category :pages
+
def perform(domain_id)
domain = PagesDomain.find_by_id(domain_id)
return unless domain&.enabled?
diff --git a/app/workers/pages_domain_verification_cron_worker.rb b/app/workers/pages_domain_verification_cron_worker.rb
index 60703c83e9e..bb3a7fede9a 100644
--- a/app/workers/pages_domain_verification_cron_worker.rb
+++ b/app/workers/pages_domain_verification_cron_worker.rb
@@ -4,6 +4,8 @@ class PagesDomainVerificationCronWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :pages
+
def perform
return if Gitlab::Database.read_only?
diff --git a/app/workers/pages_domain_verification_worker.rb b/app/workers/pages_domain_verification_worker.rb
index 7817b2ee5fc..b0888036498 100644
--- a/app/workers/pages_domain_verification_worker.rb
+++ b/app/workers/pages_domain_verification_worker.rb
@@ -3,6 +3,8 @@
class PagesDomainVerificationWorker
include ApplicationWorker
+ feature_category :pages
+
# rubocop: disable CodeReuse/ActiveRecord
def perform(domain_id)
return if Gitlab::Database.read_only?
diff --git a/app/workers/pages_worker.rb b/app/workers/pages_worker.rb
index fa0dfa2ff4b..484d9053849 100644
--- a/app/workers/pages_worker.rb
+++ b/app/workers/pages_worker.rb
@@ -4,6 +4,7 @@ class PagesWorker
include ApplicationWorker
sidekiq_options retry: 3
+ feature_category :pages
def perform(action, *arg)
send(action, *arg) # rubocop:disable GitlabSecurity/PublicSend
diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb
index 96524d93f8d..96f3725dbbe 100644
--- a/app/workers/pipeline_process_worker.rb
+++ b/app/workers/pipeline_process_worker.rb
@@ -5,6 +5,7 @@ class PipelineProcessWorker
include PipelineQueue
queue_namespace :pipeline_processing
+ feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
def perform(pipeline_id, build_ids = nil)
diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb
index 9410fd1a786..f500ea08353 100644
--- a/app/workers/pipeline_schedule_worker.rb
+++ b/app/workers/pipeline_schedule_worker.rb
@@ -4,6 +4,8 @@ class PipelineScheduleWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :continuous_integration
+
def perform
Ci::PipelineSchedule.runnable_schedules.preloaded.find_in_batches do |schedules|
schedules.each do |schedule|
diff --git a/app/workers/plugin_worker.rb b/app/workers/plugin_worker.rb
index c293e28be4a..e708031abdf 100644
--- a/app/workers/plugin_worker.rb
+++ b/app/workers/plugin_worker.rb
@@ -4,6 +4,7 @@ class PluginWorker
include ApplicationWorker
sidekiq_options retry: false
+ feature_category :integrations
def perform(file_name, data)
success, message = Gitlab::Plugin.execute(file_name, data)
diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb
index 4f193e95faa..a3bc7e5b9c9 100644
--- a/app/workers/post_receive.rb
+++ b/app/workers/post_receive.rb
@@ -3,6 +3,8 @@
class PostReceive
include ApplicationWorker
+ feature_category :source_code_management
+
def perform(gl_repository, identifier, changes, push_options = {})
project, repo_type = Gitlab::GlRepository.parse(gl_repository)
diff --git a/app/workers/process_commit_worker.rb b/app/workers/process_commit_worker.rb
index f6ebe4ab006..1e4561fc6ea 100644
--- a/app/workers/process_commit_worker.rb
+++ b/app/workers/process_commit_worker.rb
@@ -10,6 +10,8 @@
class ProcessCommitWorker
include ApplicationWorker
+ feature_category :source_code_management
+
# project_id - The ID of the project this commit belongs to.
# user_id - The ID of the user that pushed the commit.
# commit_hash - Hash containing commit details to use for constructing a
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index e3f1f61991c..57a01c0dd8e 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -5,6 +5,8 @@ class ProjectCacheWorker
include ApplicationWorker
LEASE_TIMEOUT = 15.minutes.to_i
+ feature_category :source_code_management
+
# project_id - The ID of the project for which to flush the cache.
# files - An Array containing extra types of files to refresh such as
# `:readme` to flush the README and `:changelog` to flush the
diff --git a/app/workers/project_daily_statistics_worker.rb b/app/workers/project_daily_statistics_worker.rb
index 101f5c28459..19c2fd67763 100644
--- a/app/workers/project_daily_statistics_worker.rb
+++ b/app/workers/project_daily_statistics_worker.rb
@@ -3,6 +3,8 @@
class ProjectDailyStatisticsWorker
include ApplicationWorker
+ feature_category :source_code_management
+
def perform(project_id)
project = Project.find_by_id(project_id)
diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb
index 4447e867240..1d20837faa2 100644
--- a/app/workers/project_destroy_worker.rb
+++ b/app/workers/project_destroy_worker.rb
@@ -4,6 +4,8 @@ class ProjectDestroyWorker
include ApplicationWorker
include ExceptionBacktrace
+ feature_category :source_code_management
+
def perform(project_id, user_id, params)
project = Project.find(project_id)
user = User.find(user_id)
diff --git a/app/workers/project_export_worker.rb b/app/workers/project_export_worker.rb
index ed9da39c7c3..bbcf3b72718 100644
--- a/app/workers/project_export_worker.rb
+++ b/app/workers/project_export_worker.rb
@@ -5,6 +5,7 @@ class ProjectExportWorker
include ExceptionBacktrace
sidekiq_options retry: 3
+ feature_category :source_code_management
def perform(current_user_id, project_id, after_export_strategy = {}, params = {})
current_user = User.find(current_user_id)
diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb
index 25567cec08b..8041404fc71 100644
--- a/app/workers/project_service_worker.rb
+++ b/app/workers/project_service_worker.rb
@@ -4,6 +4,7 @@ class ProjectServiceWorker
include ApplicationWorker
sidekiq_options dead: false
+ feature_category :integrations
def perform(hook_id, data)
data = data.with_indifferent_access
diff --git a/app/workers/propagate_service_template_worker.rb b/app/workers/propagate_service_template_worker.rb
index 3ccd7615697..73a2b453207 100644
--- a/app/workers/propagate_service_template_worker.rb
+++ b/app/workers/propagate_service_template_worker.rb
@@ -4,6 +4,8 @@
class PropagateServiceTemplateWorker
include ApplicationWorker
+ feature_category :source_code_management
+
LEASE_TIMEOUT = 4.hours.to_i
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb
index e2d1fb3ed35..f421e8dbf59 100644
--- a/app/workers/prune_old_events_worker.rb
+++ b/app/workers/prune_old_events_worker.rb
@@ -4,6 +4,8 @@ class PruneOldEventsWorker
include ApplicationWorker
include CronjobQueue
+ feature_category_not_owned!
+
# rubocop: disable CodeReuse/ActiveRecord
def perform
# Contribution calendar shows maximum 12 months of events, we retain 3 years for data integrity.
diff --git a/app/workers/prune_web_hook_logs_worker.rb b/app/workers/prune_web_hook_logs_worker.rb
index 38054069f4e..8e48b45fc34 100644
--- a/app/workers/prune_web_hook_logs_worker.rb
+++ b/app/workers/prune_web_hook_logs_worker.rb
@@ -6,6 +6,8 @@ class PruneWebHookLogsWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :integrations
+
# The maximum number of rows to remove in a single job.
DELETE_LIMIT = 50_000
diff --git a/app/workers/reactive_caching_worker.rb b/app/workers/reactive_caching_worker.rb
index b30864db802..af4a3def062 100644
--- a/app/workers/reactive_caching_worker.rb
+++ b/app/workers/reactive_caching_worker.rb
@@ -3,6 +3,8 @@
class ReactiveCachingWorker
include ApplicationWorker
+ feature_category_not_owned!
+
def perform(class_name, id, *args)
klass = begin
class_name.constantize
diff --git a/app/workers/rebase_worker.rb b/app/workers/rebase_worker.rb
index 8d06adcd993..7343226fdcd 100644
--- a/app/workers/rebase_worker.rb
+++ b/app/workers/rebase_worker.rb
@@ -5,6 +5,8 @@
class RebaseWorker
include ApplicationWorker
+ feature_category :source_code_management
+
def perform(merge_request_id, current_user_id)
current_user = User.find(current_user_id)
merge_request = MergeRequest.find(merge_request_id)
diff --git a/app/workers/remote_mirror_notification_worker.rb b/app/workers/remote_mirror_notification_worker.rb
index 368abfeda99..8bc19230caf 100644
--- a/app/workers/remote_mirror_notification_worker.rb
+++ b/app/workers/remote_mirror_notification_worker.rb
@@ -3,6 +3,8 @@
class RemoteMirrorNotificationWorker
include ApplicationWorker
+ feature_category :source_code_management
+
def perform(remote_mirror_id)
remote_mirror = RemoteMirror.find_by_id(remote_mirror_id)
diff --git a/app/workers/remove_expired_group_links_worker.rb b/app/workers/remove_expired_group_links_worker.rb
index 25128caf72f..147b412b772 100644
--- a/app/workers/remove_expired_group_links_worker.rb
+++ b/app/workers/remove_expired_group_links_worker.rb
@@ -4,6 +4,8 @@ class RemoveExpiredGroupLinksWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :authentication_and_authorization
+
def perform
ProjectGroupLink.expired.destroy_all # rubocop: disable DestroyAll
end
diff --git a/app/workers/remove_expired_members_worker.rb b/app/workers/remove_expired_members_worker.rb
index 3497a1f9280..75f06fd9f6b 100644
--- a/app/workers/remove_expired_members_worker.rb
+++ b/app/workers/remove_expired_members_worker.rb
@@ -4,6 +4,8 @@ class RemoveExpiredMembersWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :authentication_and_authorization
+
def perform
Member.expired.find_each do |member|
Members::DestroyService.new.execute(member, skip_authorization: true)
diff --git a/app/workers/remove_unreferenced_lfs_objects_worker.rb b/app/workers/remove_unreferenced_lfs_objects_worker.rb
index 95e7a9f537f..7f2c23f4685 100644
--- a/app/workers/remove_unreferenced_lfs_objects_worker.rb
+++ b/app/workers/remove_unreferenced_lfs_objects_worker.rb
@@ -4,6 +4,8 @@ class RemoveUnreferencedLfsObjectsWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :source_code_management
+
def perform
LfsObject.destroy_unreferenced
end
diff --git a/app/workers/repository_archive_cache_worker.rb b/app/workers/repository_archive_cache_worker.rb
index c1dff8ced90..ebc83c1b17a 100644
--- a/app/workers/repository_archive_cache_worker.rb
+++ b/app/workers/repository_archive_cache_worker.rb
@@ -4,6 +4,8 @@ class RepositoryArchiveCacheWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :source_code_management
+
def perform
RepositoryArchiveCleanUpService.new.execute
end
diff --git a/app/workers/repository_check/dispatch_worker.rb b/app/workers/repository_check/dispatch_worker.rb
index 0a7d9a14c6a..d2bd5f9b967 100644
--- a/app/workers/repository_check/dispatch_worker.rb
+++ b/app/workers/repository_check/dispatch_worker.rb
@@ -7,6 +7,8 @@ module RepositoryCheck
include ::EachShardWorker
include ExclusiveLeaseGuard
+ feature_category :source_code_management
+
LEASE_TIMEOUT = 1.hour
def perform
diff --git a/app/workers/repository_cleanup_worker.rb b/app/workers/repository_cleanup_worker.rb
index aa26c173a72..dd2cbd42d1f 100644
--- a/app/workers/repository_cleanup_worker.rb
+++ b/app/workers/repository_cleanup_worker.rb
@@ -4,6 +4,7 @@ class RepositoryCleanupWorker
include ApplicationWorker
sidekiq_options retry: 3
+ feature_category :source_code_management
sidekiq_retries_exhausted do |msg, err|
next if err.is_a?(ActiveRecord::RecordNotFound)
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index 35e9c58eb13..0adf745c7ac 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -6,6 +6,8 @@ class RepositoryForkWorker
include ProjectStartImport
include ProjectImportOptions
+ feature_category :source_code_management
+
def perform(*args)
target_project_id = args.shift
target_project = Project.find(target_project_id)
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index 85771fa8b31..bc2d0366fdd 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -6,6 +6,8 @@ class RepositoryImportWorker
include ProjectStartImport
include ProjectImportOptions
+ feature_category :importers
+
# technical debt: https://gitlab.com/gitlab-org/gitlab/issues/33991
sidekiq_options memory_killer_memory_growth_kb: ENV.fetch('MEMORY_KILLER_REPOSITORY_IMPORT_WORKER_MEMORY_GROWTH_KB', 50).to_i
sidekiq_options memory_killer_max_memory_growth_kb: ENV.fetch('MEMORY_KILLER_REPOSITORY_IMPORT_WORKER_MAX_MEMORY_GROWTH_KB', 300_000).to_i
diff --git a/app/workers/repository_remove_remote_worker.rb b/app/workers/repository_remove_remote_worker.rb
index a85e9fa9394..3e55ebc77ed 100644
--- a/app/workers/repository_remove_remote_worker.rb
+++ b/app/workers/repository_remove_remote_worker.rb
@@ -4,6 +4,8 @@ class RepositoryRemoveRemoteWorker
include ApplicationWorker
include ExclusiveLeaseGuard
+ feature_category :source_code_management
+
LEASE_TIMEOUT = 1.hour
attr_reader :project, :remote_name
diff --git a/app/workers/repository_update_remote_mirror_worker.rb b/app/workers/repository_update_remote_mirror_worker.rb
index d13c7641eb3..b4d96546fa4 100644
--- a/app/workers/repository_update_remote_mirror_worker.rb
+++ b/app/workers/repository_update_remote_mirror_worker.rb
@@ -7,6 +7,7 @@ class RepositoryUpdateRemoteMirrorWorker
include Gitlab::ExclusiveLeaseHelpers
sidekiq_options retry: 3, dead: false
+ feature_category :source_code_management
LOCK_WAIT_TIME = 30.seconds
MAX_TRIES = 3
diff --git a/app/workers/requests_profiles_worker.rb b/app/workers/requests_profiles_worker.rb
index ae022d43e29..6ab020afb10 100644
--- a/app/workers/requests_profiles_worker.rb
+++ b/app/workers/requests_profiles_worker.rb
@@ -4,6 +4,8 @@ class RequestsProfilesWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :source_code_management
+
def perform
Gitlab::RequestProfiler.remove_all_profiles
end
diff --git a/app/workers/run_pipeline_schedule_worker.rb b/app/workers/run_pipeline_schedule_worker.rb
index 659f8b80397..853f774875a 100644
--- a/app/workers/run_pipeline_schedule_worker.rb
+++ b/app/workers/run_pipeline_schedule_worker.rb
@@ -5,6 +5,7 @@ class RunPipelineScheduleWorker
include PipelineQueue
queue_namespace :pipeline_creation
+ feature_category :continuous_integration
# rubocop: disable CodeReuse/ActiveRecord
def perform(schedule_id, user_id)
diff --git a/app/workers/schedule_migrate_external_diffs_worker.rb b/app/workers/schedule_migrate_external_diffs_worker.rb
index 04a370f01af..8abb5922b54 100644
--- a/app/workers/schedule_migrate_external_diffs_worker.rb
+++ b/app/workers/schedule_migrate_external_diffs_worker.rb
@@ -5,6 +5,8 @@ class ScheduleMigrateExternalDiffsWorker
include CronjobQueue
include Gitlab::ExclusiveLeaseHelpers
+ feature_category :source_code_management
+
def perform
in_lock(self.class.name.underscore, ttl: 2.hours, retries: 0) do
MergeRequests::MigrateExternalDiffsService.enqueue!
diff --git a/app/workers/stuck_ci_jobs_worker.rb b/app/workers/stuck_ci_jobs_worker.rb
index 7e002d8822c..971edb1f14f 100644
--- a/app/workers/stuck_ci_jobs_worker.rb
+++ b/app/workers/stuck_ci_jobs_worker.rb
@@ -4,6 +4,8 @@ class StuckCiJobsWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :continuous_integration
+
EXCLUSIVE_LEASE_KEY = 'stuck_ci_builds_worker_lease'
BUILD_RUNNING_OUTDATED_TIMEOUT = 1.hour
diff --git a/app/workers/stuck_import_jobs_worker.rb b/app/workers/stuck_import_jobs_worker.rb
index a9ff5b22b25..4993cd1220c 100644
--- a/app/workers/stuck_import_jobs_worker.rb
+++ b/app/workers/stuck_import_jobs_worker.rb
@@ -4,6 +4,8 @@ class StuckImportJobsWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :importers
+
IMPORT_JOBS_EXPIRATION = 15.hours.to_i
def perform
diff --git a/app/workers/stuck_merge_jobs_worker.rb b/app/workers/stuck_merge_jobs_worker.rb
index e840ae47421..024863ab530 100644
--- a/app/workers/stuck_merge_jobs_worker.rb
+++ b/app/workers/stuck_merge_jobs_worker.rb
@@ -4,6 +4,8 @@ class StuckMergeJobsWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :source_code_management
+
def self.logger
Rails.logger # rubocop:disable Gitlab/RailsLogger
end
@@ -31,7 +33,7 @@ class StuckMergeJobsWorker
def apply_current_state!(completed_jids, completed_ids)
merge_requests = MergeRequest.where(id: completed_ids)
- merge_requests.where.not(merge_commit_sha: nil).update_all(state: :merged)
+ merge_requests.where.not(merge_commit_sha: nil).update_all(state_id: MergeRequest.available_states[:merged])
merge_requests_to_reopen = merge_requests.where(merge_commit_sha: nil)
diff --git a/app/workers/system_hook_push_worker.rb b/app/workers/system_hook_push_worker.rb
index 15e369ebcfb..fc6237f359a 100644
--- a/app/workers/system_hook_push_worker.rb
+++ b/app/workers/system_hook_push_worker.rb
@@ -3,6 +3,8 @@
class SystemHookPushWorker
include ApplicationWorker
+ feature_category :source_code_management
+
def perform(push_data, hook_id)
SystemHooksService.new.execute_hooks(push_data, hook_id)
end
diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb
index 55b599ba38f..4c8ee1ee425 100644
--- a/app/workers/trending_projects_worker.rb
+++ b/app/workers/trending_projects_worker.rb
@@ -4,6 +4,8 @@ class TrendingProjectsWorker
include ApplicationWorker
include CronjobQueue
+ feature_category :source_code_management
+
def perform
Rails.logger.info('Refreshing trending projects') # rubocop:disable Gitlab/RailsLogger
diff --git a/app/workers/update_external_pull_requests_worker.rb b/app/workers/update_external_pull_requests_worker.rb
index c5acfa82ada..8b0952528fa 100644
--- a/app/workers/update_external_pull_requests_worker.rb
+++ b/app/workers/update_external_pull_requests_worker.rb
@@ -3,6 +3,8 @@
class UpdateExternalPullRequestsWorker
include ApplicationWorker
+ feature_category :source_code_management
+
def perform(project_id, user_id, ref)
project = Project.find_by_id(project_id)
return unless project
diff --git a/app/workers/update_head_pipeline_for_merge_request_worker.rb b/app/workers/update_head_pipeline_for_merge_request_worker.rb
index 4ec2b9d8fbe..77859abfea4 100644
--- a/app/workers/update_head_pipeline_for_merge_request_worker.rb
+++ b/app/workers/update_head_pipeline_for_merge_request_worker.rb
@@ -5,6 +5,7 @@ class UpdateHeadPipelineForMergeRequestWorker
include PipelineQueue
queue_namespace :pipeline_processing
+ feature_category :continuous_integration
def perform(merge_request_id)
MergeRequest.find_by_id(merge_request_id).try do |merge_request|
diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb
index 6c0e472e05a..8e1703cdd0b 100644
--- a/app/workers/update_merge_requests_worker.rb
+++ b/app/workers/update_merge_requests_worker.rb
@@ -3,6 +3,8 @@
class UpdateMergeRequestsWorker
include ApplicationWorker
+ feature_category :source_code_management
+
LOG_TIME_THRESHOLD = 90 # seconds
# rubocop: disable CodeReuse/ActiveRecord
diff --git a/app/workers/update_project_statistics_worker.rb b/app/workers/update_project_statistics_worker.rb
index 3abb7e34a9d..e36cebf6f4f 100644
--- a/app/workers/update_project_statistics_worker.rb
+++ b/app/workers/update_project_statistics_worker.rb
@@ -4,6 +4,8 @@
class UpdateProjectStatisticsWorker
include ApplicationWorker
+ feature_category :source_code_management
+
# project_id - The ID of the project for which to flush the cache.
# statistics - An Array containing columns from ProjectStatistics to
# refresh, if empty all columns will be refreshed
diff --git a/app/workers/upload_checksum_worker.rb b/app/workers/upload_checksum_worker.rb
index 834dcaa435d..d35367145b8 100644
--- a/app/workers/upload_checksum_worker.rb
+++ b/app/workers/upload_checksum_worker.rb
@@ -3,6 +3,8 @@
class UploadChecksumWorker
include ApplicationWorker
+ feature_category :geo_replication
+
def perform(upload_id)
upload = Upload.find(upload_id)
upload.calculate_checksum!
diff --git a/app/workers/web_hook_worker.rb b/app/workers/web_hook_worker.rb
index 09219a24a16..fd7ca93683e 100644
--- a/app/workers/web_hook_worker.rb
+++ b/app/workers/web_hook_worker.rb
@@ -3,6 +3,7 @@
class WebHookWorker
include ApplicationWorker
+ feature_category :integrations
sidekiq_options retry: 4, dead: false
def perform(hook_id, data, hook_name)
diff --git a/changelogs/unreleased/14064-commit-status-on-any-pipelines.yml b/changelogs/unreleased/14064-commit-status-on-any-pipelines.yml
new file mode 100644
index 00000000000..db55148cf65
--- /dev/null
+++ b/changelogs/unreleased/14064-commit-status-on-any-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Make commit status created for any pipelines
+merge_request: 17524
+author: Aufar Gilbran
+type: changed
diff --git a/changelogs/unreleased/28211-check-if-mapping-is-empty-before-caching.yml b/changelogs/unreleased/28211-check-if-mapping-is-empty-before-caching.yml
new file mode 100644
index 00000000000..efdd47bb061
--- /dev/null
+++ b/changelogs/unreleased/28211-check-if-mapping-is-empty-before-caching.yml
@@ -0,0 +1,5 @@
+---
+title: Check if mapping is empty before caching in File Collections
+merge_request: 18290
+author: briankabiro
+type: performance
diff --git a/changelogs/unreleased/29513-continue-improvements-for-time-window-filtering-on-metrics-dashboar.yml b/changelogs/unreleased/29513-continue-improvements-for-time-window-filtering-on-metrics-dashboar.yml
new file mode 100644
index 00000000000..668e25f4749
--- /dev/null
+++ b/changelogs/unreleased/29513-continue-improvements-for-time-window-filtering-on-metrics-dashboar.yml
@@ -0,0 +1,5 @@
+---
+title: Improve time window filtering on metrics dashboard
+merge_request: 17554
+author:
+type: added
diff --git a/changelogs/unreleased/ac-fix-only-os-uplods.yml b/changelogs/unreleased/ac-fix-only-os-uplods.yml
new file mode 100644
index 00000000000..d63ddc059b6
--- /dev/null
+++ b/changelogs/unreleased/ac-fix-only-os-uplods.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid dumping files on disk when direct_upload is enabled
+merge_request: 18135
+author:
+type: performance
diff --git a/changelogs/unreleased/an-sidekiq-job-feature-attribution.yml b/changelogs/unreleased/an-sidekiq-job-feature-attribution.yml
new file mode 100644
index 00000000000..6f5832dfef2
--- /dev/null
+++ b/changelogs/unreleased/an-sidekiq-job-feature-attribution.yml
@@ -0,0 +1,5 @@
+---
+title: Attribute each Sidekiq worker to a feature category
+merge_request: 18462
+author:
+type: other
diff --git a/changelogs/unreleased/issue_28457.yml b/changelogs/unreleased/issue_28457.yml
new file mode 100644
index 00000000000..51e19660ffc
--- /dev/null
+++ b/changelogs/unreleased/issue_28457.yml
@@ -0,0 +1,5 @@
+---
+title: Deprecate usage of state column for issues and merge requests
+merge_request: 18099
+author:
+type: changed
diff --git a/changelogs/unreleased/stop-showing-new-issue-button-when-project-is-archived.yml b/changelogs/unreleased/stop-showing-new-issue-button-when-project-is-archived.yml
new file mode 100644
index 00000000000..f786df41528
--- /dev/null
+++ b/changelogs/unreleased/stop-showing-new-issue-button-when-project-is-archived.yml
@@ -0,0 +1,5 @@
+---
+title: Do not show new issue button on archived projects
+merge_request: 18590
+author:
+type: changed
diff --git a/config/feature_categories.yml b/config/feature_categories.yml
new file mode 100644
index 00000000000..59752a81f60
--- /dev/null
+++ b/config/feature_categories.yml
@@ -0,0 +1,103 @@
+#
+# This file contains a list of all feature categories in GitLab
+# It is generated from the stages file at https://gitlab.com/gitlab-com/www-gitlab-com/raw/master/data/stages.yml.
+# If you would like to update it, please run
+# `./scripts/update-feature-categories` to generate a new copy
+#
+# PLEASE DO NOT EDIT THIS FILE MANUALLY.
+#
+---
+- accessibility_testing
+- account-management
+- agile_portfolio_management
+- analysis
+- audit_management
+- authentication_and_authorization
+- auto_devops
+- backup_restore
+- behavior_analytics
+- chaos_engineering
+- chatops
+- cloud_native_installation
+- cluster_cost_optimization
+- cluster_monitoring
+- code_analytics
+- code_quality
+- code_review
+- collection
+- container_network_security
+- container_registry
+- container_scanning
+- continuous_delivery
+- continuous_integration
+- data_loss_prevention
+- dependency_proxy
+- dependency_scanning
+- design_management
+- devops_score
+- disaster_recovery
+- dynamic_application_security_testing
+- error_tracking
+- feature_flags
+- fuzzing
+- geo_replication
+- gitaly
+- gitter
+- groups
+- helm_chart_registry
+- importers
+- incident_management
+- incremental_rollout
+- infrastructure_as_code
+- integration_testing
+- integrations
+- interactive_application_security_testing
+- internationalization
+- issue_tracking
+- kanban_boards
+- kubernetes_configuration
+- language_specific
+- license_compliance
+- live_coding
+- load_testing
+- logging
+- metrics
+- omnibus_package
+- package_registry
+- pages
+- quality_management
+- release_governance
+- release_orchestration
+- requirements_management
+- review_apps
+- runbooks
+- runner
+- runtime_application_self_protection
+- sdk
+- search
+- secret_detection
+- secrets_management
+- serverless
+- service_desk
+- snippets
+- source_code_management
+- static_application_security_testing
+- status_page
+- storage_security
+- synthetic_monitoring
+- system_testing
+- templates
+- threat_detection
+- time_tracking
+- tracing
+- unit_testing
+- usability_testing
+- users
+- value_stream_management
+- vulnerability_database
+- vulnerability_management
+- web_firewall
+- web_ide
+- web_performance
+- wiki
+- workflow_policies
diff --git a/db/migrate/20190927055500_create_description_versions.rb b/db/migrate/20190927055500_create_description_versions.rb
new file mode 100644
index 00000000000..6ad34d4a89e
--- /dev/null
+++ b/db/migrate/20190927055500_create_description_versions.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+class CreateDescriptionVersions < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ create_table :description_versions do |t|
+ t.timestamps_with_timezone
+ t.references :issue, index: false, foreign_key: { on_delete: :cascade }, type: :integer
+ t.references :merge_request, index: false, foreign_key: { on_delete: :cascade }, type: :integer
+ t.references :epic, index: false, foreign_key: { on_delete: :cascade }, type: :integer
+ t.text :description
+ end
+
+ add_index :description_versions, :issue_id, where: 'issue_id IS NOT NULL'
+ add_index :description_versions, :merge_request_id, where: 'merge_request_id IS NOT NULL'
+ add_index :description_versions, :epic_id, where: 'epic_id IS NOT NULL'
+
+ add_column :system_note_metadata, :description_version_id, :bigint
+ end
+
+ def down
+ remove_column :system_note_metadata, :description_version_id
+
+ drop_table :description_versions
+ end
+end
diff --git a/db/migrate/20190927055540_add_index_to_sytem_note_metadata_description_version_id.rb b/db/migrate/20190927055540_add_index_to_sytem_note_metadata_description_version_id.rb
new file mode 100644
index 00000000000..695ba955043
--- /dev/null
+++ b/db/migrate/20190927055540_add_index_to_sytem_note_metadata_description_version_id.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+class AddIndexToSytemNoteMetadataDescriptionVersionId < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :system_note_metadata, :description_version_id, unique: true, where: 'description_version_id IS NOT NULL'
+ add_concurrent_foreign_key :system_note_metadata, :description_versions, column: :description_version_id, on_delete: :nullify
+ end
+
+ def down
+ remove_foreign_key :system_note_metadata, column: :description_version_id
+ remove_concurrent_index :system_note_metadata, :description_version_id
+ end
+end
diff --git a/db/migrate/20191008180203_add_issuable_state_id_indexes.rb b/db/migrate/20191008180203_add_issuable_state_id_indexes.rb
new file mode 100644
index 00000000000..a9a8b8b6359
--- /dev/null
+++ b/db/migrate/20191008180203_add_issuable_state_id_indexes.rb
@@ -0,0 +1,75 @@
+# frozen_string_literal: true
+
+class AddIssuableStateIdIndexes < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ # Creates the same indexes that are currently using state:string column
+ # for issues and merge_requests tables
+ create_indexes_for_issues
+ create_indexes_for_merge_requests
+ end
+
+ def down
+ # Removes indexes for issues
+ remove_concurrent_index_by_name :issues, 'idx_issues_on_state_id'
+ remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_created_at_and_id_and_state_id'
+ remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_due_date_and_id_and_state_id'
+ remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_rel_position_and_state_id_and_id'
+ remove_concurrent_index_by_name :issues, 'idx_issues_on_project_id_and_updated_at_and_id_and_state_id'
+
+ # Removes indexes from merge_requests
+ remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_id_and_merge_jid'
+ remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_source_project_and_branch_state_opened'
+ remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_state_id_and_merge_status'
+ remove_concurrent_index_by_name :merge_requests, 'idx_merge_requests_on_target_project_id_and_iid_opened'
+ end
+
+ def create_indexes_for_issues
+ add_concurrent_index :issues, :state_id, name: 'idx_issues_on_state_id'
+
+ add_concurrent_index :issues,
+ [:project_id, :created_at, :id, :state_id],
+ name: 'idx_issues_on_project_id_and_created_at_and_id_and_state_id'
+
+ add_concurrent_index :issues,
+ [:project_id, :due_date, :id, :state_id],
+ where: 'due_date IS NOT NULL',
+ name: 'idx_issues_on_project_id_and_due_date_and_id_and_state_id'
+
+ add_concurrent_index :issues,
+ [:project_id, :relative_position, :state_id, :id],
+ order: { id: :desc },
+ name: 'idx_issues_on_project_id_and_rel_position_and_state_id_and_id'
+
+ add_concurrent_index :issues,
+ [:project_id, :updated_at, :id, :state_id],
+ name: 'idx_issues_on_project_id_and_updated_at_and_id_and_state_id'
+ end
+
+ def create_indexes_for_merge_requests
+ add_concurrent_index :merge_requests,
+ [:id, :merge_jid],
+ where: 'merge_jid IS NOT NULL and state_id = 4',
+ name: 'idx_merge_requests_on_id_and_merge_jid'
+
+ add_concurrent_index :merge_requests,
+ [:source_project_id, :source_branch],
+ where: 'state_id = 1',
+ name: 'idx_merge_requests_on_source_project_and_branch_state_opened'
+
+ add_concurrent_index :merge_requests,
+ [:state_id, :merge_status],
+ where: "state_id = 1 AND merge_status = 'can_be_merged'",
+ name: 'idx_merge_requests_on_state_id_and_merge_status'
+
+ add_concurrent_index :merge_requests,
+ [:target_project_id, :iid],
+ where: 'state_id = 1',
+ name: 'idx_merge_requests_on_target_project_id_and_iid_opened'
+ end
+end
diff --git a/db/migrate/20191008200204_add_state_id_default_value.rb b/db/migrate/20191008200204_add_state_id_default_value.rb
new file mode 100644
index 00000000000..15a80163ca8
--- /dev/null
+++ b/db/migrate/20191008200204_add_state_id_default_value.rb
@@ -0,0 +1,24 @@
+# frozen_string_literal: true
+
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddStateIdDefaultValue < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ change_column_default :issues, :state_id, 1
+ change_column_null :issues, :state_id, false
+ change_column_default :merge_requests, :state_id, 1
+ change_column_null :merge_requests, :state_id, false
+ end
+
+ def down
+ change_column_default :issues, :state_id, nil
+ change_column_null :issues, :state_id, true
+ change_column_default :merge_requests, :state_id, nil
+ change_column_null :merge_requests, :state_id, true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index e43eaef911d..ef9cd696db5 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1266,6 +1266,18 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
t.index ["project_id", "status"], name: "index_deployments_on_project_id_and_status"
end
+ create_table "description_versions", force: :cascade do |t|
+ t.datetime_with_timezone "created_at", null: false
+ t.datetime_with_timezone "updated_at", null: false
+ t.integer "issue_id"
+ t.integer "merge_request_id"
+ t.integer "epic_id"
+ t.text "description"
+ t.index ["epic_id"], name: "index_description_versions_on_epic_id", where: "(epic_id IS NOT NULL)"
+ t.index ["issue_id"], name: "index_description_versions_on_issue_id", where: "(issue_id IS NOT NULL)"
+ t.index ["merge_request_id"], name: "index_description_versions_on_merge_request_id", where: "(merge_request_id IS NOT NULL)"
+ end
+
create_table "design_management_designs", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "issue_id"
@@ -1916,7 +1928,7 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
t.boolean "discussion_locked"
t.datetime_with_timezone "closed_at"
t.integer "closed_by_id"
- t.integer "state_id", limit: 2
+ t.integer "state_id", limit: 2, default: 1, null: false
t.integer "duplicated_to_id"
t.index ["author_id"], name: "index_issues_on_author_id"
t.index ["closed_by_id"], name: "index_issues_on_closed_by_id"
@@ -1926,12 +1938,17 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
t.index ["milestone_id"], name: "index_issues_on_milestone_id"
t.index ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)"
t.index ["project_id", "created_at", "id", "state"], name: "index_issues_on_project_id_and_created_at_and_id_and_state"
+ t.index ["project_id", "created_at", "id", "state_id"], name: "idx_issues_on_project_id_and_created_at_and_id_and_state_id"
t.index ["project_id", "due_date", "id", "state"], name: "idx_issues_on_project_id_and_due_date_and_id_and_state_partial", where: "(due_date IS NOT NULL)"
+ t.index ["project_id", "due_date", "id", "state_id"], name: "idx_issues_on_project_id_and_due_date_and_id_and_state_id", where: "(due_date IS NOT NULL)"
t.index ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true
t.index ["project_id", "relative_position", "state", "id"], name: "index_issues_on_project_id_and_rel_position_and_state_and_id", order: { id: :desc }
+ t.index ["project_id", "relative_position", "state_id", "id"], name: "idx_issues_on_project_id_and_rel_position_and_state_id_and_id", order: { id: :desc }
t.index ["project_id", "updated_at", "id", "state"], name: "index_issues_on_project_id_and_updated_at_and_id_and_state"
+ t.index ["project_id", "updated_at", "id", "state_id"], name: "idx_issues_on_project_id_and_updated_at_and_id_and_state_id"
t.index ["relative_position"], name: "index_issues_on_relative_position"
t.index ["state"], name: "index_issues_on_state"
+ t.index ["state_id"], name: "idx_issues_on_state_id"
t.index ["title"], name: "index_issues_on_title_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["updated_at"], name: "index_issues_on_updated_at"
t.index ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)"
@@ -2276,23 +2293,27 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
t.boolean "discussion_locked"
t.integer "latest_merge_request_diff_id"
t.boolean "allow_maintainer_to_push"
- t.integer "state_id", limit: 2
+ t.integer "state_id", limit: 2, default: 1, null: false
t.string "rebase_jid"
t.index ["assignee_id"], name: "index_merge_requests_on_assignee_id"
t.index ["author_id"], name: "index_merge_requests_on_author_id"
t.index ["created_at"], name: "index_merge_requests_on_created_at"
t.index ["description"], name: "index_merge_requests_on_description_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id"
+ t.index ["id", "merge_jid"], name: "idx_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND (state_id = 4))"
t.index ["id", "merge_jid"], name: "index_merge_requests_on_id_and_merge_jid", where: "((merge_jid IS NOT NULL) AND ((state)::text = 'locked'::text))"
t.index ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id"
t.index ["merge_user_id"], name: "index_merge_requests_on_merge_user_id", where: "(merge_user_id IS NOT NULL)"
t.index ["milestone_id"], name: "index_merge_requests_on_milestone_id"
t.index ["source_branch"], name: "index_merge_requests_on_source_branch"
+ t.index ["source_project_id", "source_branch"], name: "idx_merge_requests_on_source_project_and_branch_state_opened", where: "(state_id = 1)"
t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_and_branch_state_opened", where: "((state)::text = 'opened'::text)"
t.index ["source_project_id", "source_branch"], name: "index_merge_requests_on_source_project_id_and_source_branch"
t.index ["state", "merge_status"], name: "index_merge_requests_on_state_and_merge_status", where: "(((state)::text = 'opened'::text) AND ((merge_status)::text = 'can_be_merged'::text))"
+ t.index ["state_id", "merge_status"], name: "idx_merge_requests_on_state_id_and_merge_status", where: "((state_id = 1) AND ((merge_status)::text = 'can_be_merged'::text))"
t.index ["target_branch"], name: "index_merge_requests_on_target_branch"
t.index ["target_project_id", "created_at"], name: "index_merge_requests_target_project_id_created_at"
+ t.index ["target_project_id", "iid"], name: "idx_merge_requests_on_target_project_id_and_iid_opened", where: "(state_id = 1)"
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid", unique: true
t.index ["target_project_id", "iid"], name: "index_merge_requests_on_target_project_id_and_iid_opened", where: "((state)::text = 'opened'::text)"
t.index ["target_project_id", "merge_commit_sha", "id"], name: "index_merge_requests_on_tp_id_and_merge_commit_sha_and_id"
@@ -3494,6 +3515,8 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
t.string "action"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.bigint "description_version_id"
+ t.index ["description_version_id"], name: "index_system_note_metadata_on_description_version_id", unique: true, where: "(description_version_id IS NOT NULL)"
t.index ["note_id"], name: "index_system_note_metadata_on_note_id", unique: true
end
@@ -4089,6 +4112,9 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade
add_foreign_key "deployments", "clusters", name: "fk_289bba3222", on_delete: :nullify
add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade
+ add_foreign_key "description_versions", "epics", on_delete: :cascade
+ add_foreign_key "description_versions", "issues", on_delete: :cascade
+ add_foreign_key "description_versions", "merge_requests", on_delete: :cascade
add_foreign_key "design_management_designs", "issues", on_delete: :cascade
add_foreign_key "design_management_designs", "projects", on_delete: :cascade
add_foreign_key "design_management_designs_versions", "design_management_designs", column: "design_id", name: "fk_03c671965c", on_delete: :cascade
@@ -4325,6 +4351,7 @@ ActiveRecord::Schema.define(version: 2019_10_16_220135) do
add_foreign_key "software_license_policies", "software_licenses", on_delete: :cascade
add_foreign_key "subscriptions", "projects", on_delete: :cascade
add_foreign_key "suggestions", "notes", on_delete: :cascade
+ add_foreign_key "system_note_metadata", "description_versions", name: "fk_fbd87415c9", on_delete: :nullify
add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade
add_foreign_key "term_agreements", "application_setting_terms", column: "term_id"
add_foreign_key "term_agreements", "users", on_delete: :cascade
diff --git a/doc/api/issues.md b/doc/api/issues.md
index 12a63ce6e24..075abf6c360 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -615,6 +615,7 @@ POST /projects/:id/issues
| `merge_request_to_resolve_discussions_of` | integer | no | The IID of a merge request in which to resolve all issues. This will fill the issue with a default description and mark all discussions as resolved. When passing a description or title, these values will take precedence over the default values.|
| `discussion_to_resolve` | string | no | The ID of a discussion to resolve. This will fill in the issue with a default description and mark the discussion as resolved. Use in combination with `merge_request_to_resolve_discussions_of`. |
| `weight` **(STARTER)** | integer | no | The weight of the issue. Valid values are greater than or equal to 0. |
+| `epic_iid` **(ULTIMATE)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. |
```bash
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues?title=Issues%20with%20auth&labels=bug
@@ -716,6 +717,7 @@ PUT /projects/:id/issues/:issue_iid
| `due_date` | string | no | Date time string in the format YEAR-MONTH-DAY, e.g. `2016-03-11` |
| `weight` **(STARTER)** | integer | no | The weight of the issue. Valid values are greater than or equal to 0. 0 |
| `discussion_locked` | boolean | no | Flag indicating if the issue's discussion is locked. If the discussion is locked only project members can add or edit comments. |
+| `epic_iid` **(ULTIMATE)** | integer | no | IID of the epic to add the issue to. Valid values are greater than or equal to 0. |
```bash
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/4/issues/85?state_event=close
diff --git a/doc/development/sidekiq_style_guide.md b/doc/development/sidekiq_style_guide.md
index c181b31f069..d52a3e652e3 100644
--- a/doc/development/sidekiq_style_guide.md
+++ b/doc/development/sidekiq_style_guide.md
@@ -61,6 +61,56 @@ the extra jobs will take resources away from jobs from workers that were already
there, if the resources available to the Sidekiq process handling the namespace
are not adjusted appropriately.
+## Feature Categorization
+
+Each Sidekiq worker, or one of its ancestor classes, must declare a
+`feature_category` attribute. This attribute maps each worker to a feature
+category. This is done for error budgeting, alert routing, and team attribution
+for Sidekiq workers.
+
+The declaration uses the `feature_category` class method, as shown below.
+
+```ruby
+class SomeScheduledTaskWorker
+ include ApplicationWorker
+
+ # Declares that this feature is part of the
+ # `continuous_integration` feature category
+ feature_category :continuous_integration
+
+ # ...
+end
+```
+
+The list of value values can be found in the file `config/feature_categories.yml`.
+This file is, in turn generated from the [`stages.yml` from the GitLab Company Handbook
+source](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml).
+
+### Updating `config/feature_categories.yml`
+
+Occassionally new features will be added to GitLab stages. When this occurs, you
+can automatically update `config/feature_categories.yml` by running
+`scripts/update-feature-categories`. This script will fetch and parse
+[`stages.yml`](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/stages.yml)
+and generare a new version of the file, which needs to be checked into source control.
+
+### Excluding Sidekiq workers from feature categorization
+
+A few Sidekiq workers, that are used across all features, cannot be mapped to a
+single category. These should be declared as such using the `feature_category_not_owned!`
+ declaration, as shown below:
+
+```ruby
+class SomeCrossCuttingConcernWorker
+ include ApplicationWorker
+
+ # Declares that this worker does not map to a feature category
+ feature_category_not_owned!
+
+ # ...
+end
+```
+
## Tests
Each Sidekiq worker must be tested using RSpec, just like any other class. These
diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md
index 129876f0c8e..0823c2e02b8 100644
--- a/doc/development/testing_guide/flaky_tests.md
+++ b/doc/development/testing_guide/flaky_tests.md
@@ -80,6 +80,9 @@ This was originally implemented in: <https://gitlab.com/gitlab-org/gitlab-foss/m
- [Bis](https://gitlab.com/gitlab-org/gitlab-foss/issues/34609#note_34048715): <https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/12604>
- [Bis](https://gitlab.com/gitlab-org/gitlab-foss/issues/34698#note_34276286): <https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/12664>
- [Assert against the underlying database state instead of against a page's content](https://gitlab.com/gitlab-org/gitlab-foss/issues/31437): <https://gitlab.com/gitlab-org/gitlab-foss/merge_requests/10934>
+- In JS tests, shifting elements can cause Capybara to misclick when the element moves at the exact time Capybara sends the click
+ - [Dropdowns rendering upward or downward due to window size and scroll position](https://gitlab.com/gitlab-org/gitlab/merge_requests/17660)
+ - [Lazy loaded images can cause Capybara to misclick](https://gitlab.com/gitlab-org/gitlab/merge_requests/18713)
#### Capybara viewport size related issues
diff --git a/doc/user/analytics/productivity_analytics.md b/doc/user/analytics/productivity_analytics.md
index 87a92fcbf2a..7313a25e797 100644
--- a/doc/user/analytics/productivity_analytics.md
+++ b/doc/user/analytics/productivity_analytics.md
@@ -1,6 +1,6 @@
# Productivity Analytics **(PREMIUM)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12079) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.3 (enabled by feature flags `productivity_analytics`).
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/12079) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.3 (enabled by feature flags `productivity_analytics`, `productivity_analytics_scatterplot_enabled`).
Track development velocity with Productivity Analytics.
diff --git a/lib/api/commit_statuses.rb b/lib/api/commit_statuses.rb
index d58a5e214ed..d108c811f4b 100644
--- a/lib/api/commit_statuses.rb
+++ b/lib/api/commit_statuses.rb
@@ -58,7 +58,6 @@ module API
post ':id/statuses/:sha' do
authorize! :create_commit_status, user_project
- commit = @project.commit(params[:sha])
not_found! 'Commit' unless commit
# Since the CommitStatus is attached to Ci::Pipeline (in the future Pipeline)
@@ -68,14 +67,15 @@ module API
# If we don't receive it, we will attach the CommitStatus to
# the first found branch on that commit
+ pipeline = all_matching_pipelines.first
+
ref = params[:ref]
+ ref ||= pipeline&.ref
ref ||= @project.repository.branch_names_contains(commit.sha).first
not_found! 'References for commit' unless ref
name = params[:name] || params[:context] || 'default'
- pipeline = @project.pipeline_for(ref, commit.sha, params[:pipeline_id])
-
unless pipeline
pipeline = @project.ci_pipelines.create!(
source: :external,
@@ -126,6 +126,20 @@ module API
end
end
# rubocop: enable CodeReuse/ActiveRecord
+ helpers do
+ def commit
+ strong_memoize(:commit) do
+ user_project.commit(params[:sha])
+ end
+ end
+
+ def all_matching_pipelines
+ pipelines = user_project.ci_pipelines.newest_first(sha: commit.sha)
+ pipelines = pipelines.for_ref(params[:ref]) if params[:ref]
+ pipelines = pipelines.for_id(params[:pipeline_id]) if params[:pipeline_id]
+ pipelines
+ end
+ end
end
end
end
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 24bc73e0de5..e01ffb631ba 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -104,7 +104,7 @@ module Gitlab
iid: issue.iid,
title: issue.title,
description: description,
- state: issue.state,
+ state_id: Issue.available_states[issue.state],
author_id: gitlab_user_id(project, issue.author),
milestone: milestone,
created_at: issue.created_at,
diff --git a/lib/gitlab/discussions_diff/file_collection.rb b/lib/gitlab/discussions_diff/file_collection.rb
index 6692dd76438..7a9d4c5c0c2 100644
--- a/lib/gitlab/discussions_diff/file_collection.rb
+++ b/lib/gitlab/discussions_diff/file_collection.rb
@@ -27,12 +27,14 @@ module Gitlab
# - The cache content is not updated (there's no need to do so)
def load_highlight
ids = highlightable_collection_ids
+ return if ids.empty?
+
cached_content = read_cache(ids)
uncached_ids = ids.select.each_with_index { |_, i| cached_content[i].nil? }
mapping = highlighted_lines_by_ids(uncached_ids)
- HighlightCache.write_multiple(mapping)
+ HighlightCache.write_multiple(mapping) if mapping.any?
diffs = diff_files_indexed_by_id.values_at(*ids)
diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb
index 1e7203cb82a..4da2004b74f 100644
--- a/lib/gitlab/google_code_import/importer.rb
+++ b/lib/gitlab/google_code_import/importer.rb
@@ -117,7 +117,7 @@ module Gitlab
description: body,
author_id: project.creator_id,
assignee_ids: [assignee_id],
- state: raw_issue['state'] == 'closed' ? 'closed' : 'opened'
+ state_id: raw_issue['state'] == 'closed' ? Issue.available_states[:closed] : Issue.available_states[:opened]
)
issue_labels = ::LabelsFinder.new(nil, project_id: project.id, title: labels).execute(skip_authorization: true)
diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb
index 1f64e440141..9d9db6cf94f 100644
--- a/lib/gitlab/hook_data/issue_builder.rb
+++ b/lib/gitlab/hook_data/issue_builder.rb
@@ -27,7 +27,7 @@ module Gitlab
duplicated_to_id
project_id
relative_position
- state
+ state_id
time_estimate
title
updated_at
@@ -46,7 +46,8 @@ module Gitlab
human_time_estimate: issue.human_time_estimate,
assignee_ids: issue.assignee_ids,
assignee_id: issue.assignee_ids.first, # This key is deprecated
- labels: issue.labels_hook_attrs
+ labels: issue.labels_hook_attrs,
+ state: issue.state
}
issue.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb
index 9ec244b0960..cb85af91f75 100644
--- a/lib/gitlab/import_export/relation_factory.rb
+++ b/lib/gitlab/import_export/relation_factory.rb
@@ -292,7 +292,11 @@ module Gitlab
existing_object
else
- relation_class.new(parsed_relation_hash)
+ object = relation_class.new
+
+ # Use #assign_attributes here to call object custom setters
+ object.assign_attributes(parsed_relation_hash)
+ object
end
end
end
diff --git a/lib/gitlab/phabricator_import/base_worker.rb b/lib/gitlab/phabricator_import/base_worker.rb
index b69c65e78f8..d2c2ef8db48 100644
--- a/lib/gitlab/phabricator_import/base_worker.rb
+++ b/lib/gitlab/phabricator_import/base_worker.rb
@@ -23,6 +23,8 @@ module Gitlab
include ProjectImportOptions # This marks the project as failed after too many tries
include Gitlab::ExclusiveLeaseHelpers
+ feature_category :importers
+
class << self
def schedule(project_id, *args)
perform_async(project_id, *args)
diff --git a/lib/uploaded_file.rb b/lib/uploaded_file.rb
index aae542f02ac..424db653fb8 100644
--- a/lib/uploaded_file.rb
+++ b/lib/uploaded_file.rb
@@ -6,6 +6,7 @@ require "fileutils"
class UploadedFile
InvalidPathError = Class.new(StandardError)
+ UnknownSizeError = Class.new(StandardError)
# The filename, *not* including the path, of the "uploaded" file
attr_reader :original_filename
@@ -18,37 +19,50 @@ class UploadedFile
attr_reader :remote_id
attr_reader :sha256
-
- def initialize(path, filename: nil, content_type: "application/octet-stream", sha256: nil, remote_id: nil)
- raise InvalidPathError, "#{path} file does not exist" unless ::File.exist?(path)
+ attr_reader :size
+
+ def initialize(path, filename: nil, content_type: "application/octet-stream", sha256: nil, remote_id: nil, size: nil)
+ if path.present?
+ raise InvalidPathError, "#{path} file does not exist" unless ::File.exist?(path)
+
+ @tempfile = File.new(path, 'rb')
+ @size = @tempfile.size
+ else
+ begin
+ @size = Integer(size)
+ rescue ArgumentError, TypeError
+ raise UnknownSizeError, 'Unable to determine file size'
+ end
+ end
@content_type = content_type
- @original_filename = sanitize_filename(filename || path)
+ @original_filename = sanitize_filename(filename || path || '')
@content_type = content_type
@sha256 = sha256
@remote_id = remote_id
- @tempfile = File.new(path, 'rb')
end
def self.from_params(params, field, upload_paths)
- unless params["#{field}.path"]
- raise InvalidPathError, "file is invalid" if params["#{field}.remote_id"]
-
- return
- end
-
- file_path = File.realpath(params["#{field}.path"])
-
- paths = Array(upload_paths) << Dir.tmpdir
- unless self.allowed_path?(file_path, paths.compact)
- raise InvalidPathError, "insecure path used '#{file_path}'"
+ path = params["#{field}.path"]
+ remote_id = params["#{field}.remote_id"]
+ return if path.blank? && remote_id.blank?
+
+ file_path = nil
+ if path
+ file_path = File.realpath(path)
+
+ paths = Array(upload_paths) << Dir.tmpdir
+ unless self.allowed_path?(file_path, paths.compact)
+ raise InvalidPathError, "insecure path used '#{file_path}'"
+ end
end
UploadedFile.new(file_path,
filename: params["#{field}.name"],
content_type: params["#{field}.type"] || 'application/octet-stream',
sha256: params["#{field}.sha256"],
- remote_id: params["#{field}.remote_id"])
+ remote_id: remote_id,
+ size: params["#{field}.size"])
end
def self.allowed_path?(file_path, paths)
@@ -68,7 +82,11 @@ class UploadedFile
end
def path
- @tempfile.path
+ @tempfile&.path
+ end
+
+ def close
+ @tempfile&.close
end
alias_method :local_path, :path
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 524ac9046b5..b3f4b0f4931 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -250,6 +250,9 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
+msgid "%{from} to %{to}"
+msgstr ""
+
msgid "%{gitlab_ci_yml} not found in this commit"
msgstr ""
@@ -1776,6 +1779,9 @@ msgstr ""
msgid "Applied"
msgstr ""
+msgid "Apply"
+msgstr ""
+
msgid "Apply a label"
msgstr ""
@@ -4855,6 +4861,9 @@ msgstr ""
msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
msgstr ""
+msgid "Custom range"
+msgstr ""
+
msgid "CustomCycleAnalytics|Add a stage"
msgstr ""
@@ -7331,6 +7340,9 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Format: %{dateFormat}"
+msgstr ""
+
msgid "Forward external support email address to"
msgstr ""
@@ -10412,6 +10424,9 @@ msgstr ""
msgid "Metrics|Legend label (optional)"
msgstr ""
+msgid "Metrics|Link contains an invalid time window."
+msgstr ""
+
msgid "Metrics|Max"
msgstr ""
@@ -13379,6 +13394,9 @@ msgstr ""
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr ""
+msgid "Quick range"
+msgstr ""
+
msgid "README"
msgstr ""
@@ -17155,6 +17173,9 @@ msgstr ""
msgid "Titles and Filenames"
msgstr ""
+msgid "To"
+msgstr ""
+
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
msgstr ""
diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb
index 20d4a00c020..ff5e118cefa 100644
--- a/qa/spec/page/element_spec.rb
+++ b/qa/spec/page/element_spec.rb
@@ -113,6 +113,7 @@ describe QA::Page::Element do
describe 'data-qa selectors' do
subject { described_class.new(:my_element) }
+
it 'properly translates to a data-qa-selector' do
expect(subject.selector_css).to include(%q([data-qa-selector="my_element"]))
end
diff --git a/scripts/update-feature-categories b/scripts/update-feature-categories
new file mode 100755
index 00000000000..ed5d8dccdd6
--- /dev/null
+++ b/scripts/update-feature-categories
@@ -0,0 +1,36 @@
+#!/usr/bin/env ruby
+
+require 'uri'
+require 'net/http'
+require 'yaml'
+
+url = URI("https://gitlab.com/gitlab-com/www-gitlab-com/raw/master/data/stages.yml")
+
+http = Net::HTTP.new(url.host, url.port)
+http.use_ssl = true
+
+request = Net::HTTP::Get.new(url)
+
+response = http.request(request)
+
+stages_doc = YAML.safe_load(response.read_body)
+feature_categories = stages_doc["stages"].values
+ .flat_map { |stage| stage["groups"].values }
+ .flat_map { |group| group["categories"] }
+ .select(&:itself)
+ .uniq
+ .sort
+
+File.open("#{__dir__}/../config/feature_categories.yml", 'w') do |file|
+ file.puts(<<~HEADER_COMMENT)
+ #
+ # This file contains a list of all feature categories in GitLab
+ # It is generated from the stages file at #{url}.
+ # If you would like to update it, please run
+ # `./scripts/update-feature-categories` to generate a new copy
+ #
+ # PLEASE DO NOT EDIT THIS FILE MANUALLY.
+ #
+ HEADER_COMMENT
+ file.write(feature_categories.to_yaml)
+end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 2edc0aa5536..c9558abab33 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1180,6 +1180,7 @@ describe Projects::IssuesController do
name: emoji_name
})
end
+
let(:emoji_name) { 'thumbsup' }
it "toggles the award emoji" do
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 4db77921f24..3ab191c0032 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -713,6 +713,7 @@ describe Projects::NotesController do
end
subject { post(:toggle_award_emoji, params: request_params.merge(name: emoji_name)) }
+
let(:emoji_name) { 'thumbsup' }
it "toggles the award emoji" do
diff --git a/spec/controllers/projects/serverless/functions_controller_spec.rb b/spec/controllers/projects/serverless/functions_controller_spec.rb
index 9f1ef3a4be8..eccc8e1d5de 100644
--- a/spec/controllers/projects/serverless/functions_controller_spec.rb
+++ b/spec/controllers/projects/serverless/functions_controller_spec.rb
@@ -107,26 +107,50 @@ describe Projects::Serverless::FunctionsController do
end
end
- context 'valid data', :use_clean_rails_memory_store_caching do
- before do
- stub_kubeclient_service_pods
- stub_reactive_cache(knative_services_finder,
- {
- services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
- pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
- },
- *knative_services_finder.cache_args)
+ context 'with valid data', :use_clean_rails_memory_store_caching do
+ shared_examples 'GET #show with valid data' do
+ it 'has a valid function name' do
+ get :show, params: params({ format: :json, environment_id: "*", id: cluster.project.name })
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response).to include(
+ "name" => project.name,
+ "url" => "http://#{project.name}.#{namespace.namespace}.example.com",
+ "podcount" => 1
+ )
+ end
end
- it 'has a valid function name' do
- get :show, params: params({ format: :json, environment_id: "*", id: cluster.project.name })
- expect(response).to have_gitlab_http_status(200)
+ context 'on Knative 0.5' do
+ before do
+ stub_kubeclient_service_pods
+ stub_reactive_cache(knative_services_finder,
+ {
+ services: kube_knative_services_body(
+ legacy_knative: true,
+ namespace: namespace.namespace,
+ name: cluster.project.name
+ )["items"],
+ pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
+ },
+ *knative_services_finder.cache_args)
+ end
- expect(json_response).to include(
- "name" => project.name,
- "url" => "http://#{project.name}.#{namespace.namespace}.example.com",
- "podcount" => 1
- )
+ include_examples 'GET #show with valid data'
+ end
+
+ context 'on Knative 0.6 or 0.7' do
+ before do
+ stub_kubeclient_service_pods
+ stub_reactive_cache(knative_services_finder,
+ {
+ services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
+ pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
+ },
+ *knative_services_finder.cache_args)
+ end
+
+ include_examples 'GET #show with valid data'
end
end
end
@@ -141,38 +165,60 @@ describe Projects::Serverless::FunctionsController do
end
describe 'GET #index with data', :use_clean_rails_memory_store_caching do
- before do
- stub_kubeclient_service_pods
- stub_reactive_cache(knative_services_finder,
- {
- services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
- pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
- },
- *knative_services_finder.cache_args)
+ shared_examples 'GET #index with data' do
+ it 'has data' do
+ get :index, params: params({ format: :json })
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response).to match({
+ "knative_installed" => "checking",
+ "functions" => [
+ a_hash_including(
+ "name" => project.name,
+ "url" => "http://#{project.name}.#{namespace.namespace}.example.com"
+ )
+ ]
+ })
+ end
+
+ it 'has data in html' do
+ get :index, params: params
+
+ expect(response).to have_gitlab_http_status(200)
+ end
end
- it 'has data' do
- get :index, params: params({ format: :json })
-
- expect(response).to have_gitlab_http_status(200)
-
- expect(json_response).to match(
- {
- "knative_installed" => "checking",
- "functions" => [
- a_hash_including(
- "name" => project.name,
- "url" => "http://#{project.name}.#{namespace.namespace}.example.com"
- )
- ]
- }
- )
+ context 'on Knative 0.5' do
+ before do
+ stub_kubeclient_service_pods
+ stub_reactive_cache(knative_services_finder,
+ {
+ services: kube_knative_services_body(
+ legacy_knative: true,
+ namespace: namespace.namespace,
+ name: cluster.project.name
+ )["items"],
+ pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
+ },
+ *knative_services_finder.cache_args)
+ end
+
+ include_examples 'GET #index with data'
end
- it 'has data in html' do
- get :index, params: params
+ context 'on Knative 0.6 or 0.7' do
+ before do
+ stub_kubeclient_service_pods
+ stub_reactive_cache(knative_services_finder,
+ {
+ services: kube_knative_services_body(namespace: namespace.namespace, name: cluster.project.name)["items"],
+ pods: kube_knative_pods_body(cluster.project.name, namespace.namespace)["items"]
+ },
+ *knative_services_finder.cache_args)
+ end
- expect(response).to have_gitlab_http_status(200)
+ include_examples 'GET #index with data'
end
end
end
diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
index 93507b58910..c67e7f7dadd 100644
--- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb
@@ -78,6 +78,7 @@ describe Projects::Settings::CiCdController do
describe 'PUT #reset_registration_token' do
subject { put :reset_registration_token, params: { namespace_id: project.namespace, project_id: project } }
+
it 'resets runner registration token' do
expect { subject }.to change { project.reload.runners_token }
end
diff --git a/spec/factories/gitaly/commit.rb b/spec/factories/gitaly/commit.rb
index 954b5338846..ef5301db770 100644
--- a/spec/factories/gitaly/commit.rb
+++ b/spec/factories/gitaly/commit.rb
@@ -12,6 +12,7 @@ FactoryBot.define do
Google::Protobuf::RepeatedField.new(:string, ids)
end
subject { "My commit" }
+
body { subject + "\nMy body" }
author { build(:gitaly_commit_author) }
committer { build(:gitaly_commit_author) }
diff --git a/spec/factories/issues.rb b/spec/factories/issues.rb
index 70f480a3bcb..46910078ee5 100644
--- a/spec/factories/issues.rb
+++ b/spec/factories/issues.rb
@@ -12,7 +12,7 @@ FactoryBot.define do
end
trait :opened do
- state { :opened }
+ state_id { Issue.available_states[:opened] }
end
trait :locked do
@@ -20,10 +20,14 @@ FactoryBot.define do
end
trait :closed do
- state { :closed }
+ state_id { Issue.available_states[:closed] }
closed_at { Time.now }
end
+ after(:build) do |issue, evaluator|
+ issue.state_id = Issue.available_states[evaluator.state]
+ end
+
factory :closed_issue, traits: [:closed]
factory :reopened_issue, traits: [:opened]
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 28a3f76d485..d16e0c10671 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -40,7 +40,7 @@ FactoryBot.define do
end
trait :merged do
- state { :merged }
+ state_id { MergeRequest.available_states[:merged] }
end
trait :merged_target do
@@ -57,7 +57,7 @@ FactoryBot.define do
end
trait :closed do
- state { :closed }
+ state_id { MergeRequest.available_states[:closed] }
end
trait :closed_last_month do
@@ -69,7 +69,7 @@ FactoryBot.define do
end
trait :opened do
- state { :opened }
+ state_id { MergeRequest.available_states[:opened] }
end
trait :invalid do
@@ -78,7 +78,7 @@ FactoryBot.define do
end
trait :locked do
- state { :locked }
+ state_id { MergeRequest.available_states[:locked] }
end
trait :simple do
@@ -186,6 +186,10 @@ FactoryBot.define do
end
end
+ after(:build) do |merge_request, evaluator|
+ merge_request.state_id = MergeRequest.available_states[evaluator.state]
+ end
+
after(:create) do |merge_request, evaluator|
merge_request.cache_merge_request_closes_issues!
end
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 0cb24ef856b..5d87c9d7be8 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -93,6 +93,7 @@ describe 'Group issues page' do
end
it 'shows projects only with issues feature enabled', :js do
+ find('.empty-state .js-lazy-loaded')
find('.new-project-item-link').click
page.within('.select2-results') do
diff --git a/spec/features/security/group/internal_access_spec.rb b/spec/features/security/group/internal_access_spec.rb
index d6575ec9de1..a182b6b9d57 100644
--- a/spec/features/security/group/internal_access_spec.rb
+++ b/spec/features/security/group/internal_access_spec.rb
@@ -16,6 +16,7 @@ describe 'Internal Group access' do
describe "Group should be internal" do
describe '#internal?' do
subject { group.internal? }
+
it { is_expected.to be_truthy }
end
end
diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb
index 2dc863a6e73..5e3e9824aaa 100644
--- a/spec/features/security/group/private_access_spec.rb
+++ b/spec/features/security/group/private_access_spec.rb
@@ -16,6 +16,7 @@ describe 'Private Group access' do
describe "Group should be private" do
describe '#private?' do
subject { group.private? }
+
it { is_expected.to be_truthy }
end
end
diff --git a/spec/features/security/group/public_access_spec.rb b/spec/features/security/group/public_access_spec.rb
index 4066a19fce2..efc84205980 100644
--- a/spec/features/security/group/public_access_spec.rb
+++ b/spec/features/security/group/public_access_spec.rb
@@ -16,6 +16,7 @@ describe 'Public Group access' do
describe "Group should be public" do
describe '#public?' do
subject { group.public? }
+
it { is_expected.to be_truthy }
end
end
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index d089fa718d2..768b883a90e 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -14,6 +14,7 @@ describe "Internal Project Access" do
describe "Project should be internal" do
describe '#internal?' do
subject { project.internal? }
+
it { is_expected.to be_truthy }
end
end
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index b868cd595cb..c2d44c05a22 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -14,6 +14,7 @@ describe "Private Project Access" do
describe "Project should be private" do
describe '#private?' do
subject { project.private? }
+
it { is_expected.to be_truthy }
end
end
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 8db2f2d69e5..19f01257713 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -14,6 +14,7 @@ describe "Public Project Access" do
describe "Project should be public" do
describe '#public?' do
subject { project.public? }
+
it { is_expected.to be_truthy }
end
end
diff --git a/spec/finders/clusters/knative_services_finder_spec.rb b/spec/finders/clusters/knative_services_finder_spec.rb
index 159724b3c1f..7ad64cc3bca 100644
--- a/spec/finders/clusters/knative_services_finder_spec.rb
+++ b/spec/finders/clusters/knative_services_finder_spec.rb
@@ -77,6 +77,7 @@ describe Clusters::KnativeServicesFinder do
describe '#knative_detected' do
subject { finder.knative_detected }
+
before do
synchronous_reactive_cache(finder)
end
diff --git a/spec/finders/concerns/finder_with_cross_project_access_spec.rb b/spec/finders/concerns/finder_with_cross_project_access_spec.rb
index 7f6190f96e0..6ba98b79176 100644
--- a/spec/finders/concerns/finder_with_cross_project_access_spec.rb
+++ b/spec/finders/concerns/finder_with_cross_project_access_spec.rb
@@ -24,6 +24,7 @@ describe FinderWithCrossProjectAccess do
let(:user) { create(:user) }
subject(:finder) { finder_class.new(user) }
+
let!(:result) { create(:issue) }
before do
diff --git a/spec/fixtures/lib/gitlab/import_export/project.group.json b/spec/fixtures/lib/gitlab/import_export/project.group.json
index 66f5bb4c87b..47faf271cca 100644
--- a/spec/fixtures/lib/gitlab/import_export/project.group.json
+++ b/spec/fixtures/lib/gitlab/import_export/project.group.json
@@ -129,7 +129,7 @@
"updated_at": "2017-08-15T18:37:40.807Z",
"branch_name": null,
"description": "Quam totam fuga numquam in eveniet.",
- "state": "opened",
+ "state": "closed",
"iid": 2,
"updated_by_id": 1,
"confidential": false,
diff --git a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_input_spec.js b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_input_spec.js
new file mode 100644
index 00000000000..1315e1226a4
--- /dev/null
+++ b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_input_spec.js
@@ -0,0 +1,66 @@
+import { mount } from '@vue/test-utils';
+import DateTimePickerInput from '~/monitoring/components/date_time_picker/date_time_picker_input.vue';
+
+const inputLabel = 'This is a label';
+const inputValue = 'something';
+
+describe('DateTimePickerInput', () => {
+ let wrapper;
+
+ const createComponent = (propsData = {}) => {
+ wrapper = mount(DateTimePickerInput, {
+ propsData: {
+ state: null,
+ value: '',
+ label: '',
+ ...propsData,
+ },
+ sync: false,
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('renders label above the input', () => {
+ createComponent({
+ label: inputLabel,
+ });
+
+ expect(wrapper.find('.gl-form-group label').text()).toBe(inputLabel);
+ });
+
+ it('renders the same `ID` for input and `for` for label', () => {
+ createComponent({ label: inputLabel });
+
+ expect(wrapper.find('.gl-form-group label').attributes('for')).toBe(
+ wrapper.find('input').attributes('id'),
+ );
+ });
+
+ it('renders valid input in gray color instead of green', () => {
+ createComponent({
+ state: true,
+ });
+
+ expect(wrapper.find('input').classes('is-valid')).toBe(false);
+ });
+
+ it('renders invalid input in red color', () => {
+ createComponent({
+ state: false,
+ });
+
+ expect(wrapper.find('input').classes('is-invalid')).toBe(true);
+ });
+
+ it('input event is emitted when focus is lost', () => {
+ createComponent();
+ jest.spyOn(wrapper.vm, '$emit');
+ wrapper.find('input').setValue(inputValue);
+ wrapper.find('input').trigger('blur');
+
+ expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', inputValue);
+ });
+});
diff --git a/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js
new file mode 100644
index 00000000000..be544435671
--- /dev/null
+++ b/spec/frontend/monitoring/components/date_time_picker/date_time_picker_spec.js
@@ -0,0 +1,157 @@
+import { mount } from '@vue/test-utils';
+import DateTimePicker from '~/monitoring/components/date_time_picker/date_time_picker.vue';
+import { timeWindows } from '~/monitoring/constants';
+
+const timeWindowsCount = Object.keys(timeWindows).length;
+const selectedTimeWindow = {
+ start: '2019-10-10T07:00:00.000Z',
+ end: '2019-10-13T07:00:00.000Z',
+};
+const selectedTimeWindowText = `3 days`;
+
+describe('DateTimePicker', () => {
+ let dateTimePicker;
+
+ const dropdownToggle = () => dateTimePicker.find('.dropdown-toggle');
+ const dropdownMenu = () => dateTimePicker.find('.dropdown-menu');
+ const applyButtonElement = () => dateTimePicker.find('button[variant="success"]').element;
+ const cancelButtonElement = () => dateTimePicker.find('button.btn-secondary').element;
+ const fillInputAndBlur = (input, val) => {
+ dateTimePicker.find(input).setValue(val);
+ dateTimePicker.find(input).trigger('blur');
+ };
+
+ const createComponent = props => {
+ dateTimePicker = mount(DateTimePicker, {
+ propsData: {
+ timeWindows,
+ selectedTimeWindow,
+ ...props,
+ },
+ sync: false,
+ });
+ };
+
+ afterEach(() => {
+ dateTimePicker.destroy();
+ });
+
+ it('renders dropdown toggle button with selected text', done => {
+ createComponent();
+ dateTimePicker.vm.$nextTick(() => {
+ expect(dropdownToggle().text()).toBe(selectedTimeWindowText);
+ done();
+ });
+ });
+
+ it('renders dropdown with 2 custom time range inputs', () => {
+ createComponent();
+ dateTimePicker.vm.$nextTick(() => {
+ expect(dateTimePicker.findAll('input').length).toBe(2);
+ });
+ });
+
+ it('renders inputs with h/m/s truncated if its all 0s', done => {
+ createComponent({
+ selectedTimeWindow: {
+ start: '2019-10-10T00:00:00.000Z',
+ end: '2019-10-14T00:10:00.000Z',
+ },
+ });
+ dateTimePicker.vm.$nextTick(() => {
+ expect(dateTimePicker.find('#custom-time-from').element.value).toBe('2019-10-10');
+ expect(dateTimePicker.find('#custom-time-to').element.value).toBe('2019-10-14 00:10:00');
+ done();
+ });
+ });
+
+ it(`renders dropdown with ${timeWindowsCount} items in quick range`, done => {
+ createComponent();
+ dropdownToggle().trigger('click');
+ dateTimePicker.vm.$nextTick(() => {
+ expect(dateTimePicker.findAll('.dropdown-item').length).toBe(timeWindowsCount);
+ done();
+ });
+ });
+
+ it(`renders dropdown with correct quick range item selected`, done => {
+ createComponent();
+ dropdownToggle().trigger('click');
+ dateTimePicker.vm.$nextTick(() => {
+ expect(dateTimePicker.find('.dropdown-item.active').text()).toBe(selectedTimeWindowText);
+
+ expect(dateTimePicker.find('.dropdown-item.active svg').isVisible()).toBe(true);
+ done();
+ });
+ });
+
+ it('renders a disabled apply button on load', () => {
+ createComponent();
+
+ expect(applyButtonElement().getAttribute('disabled')).toBe('disabled');
+ });
+
+ it('displays inline error message if custom time range inputs are invalid', done => {
+ createComponent();
+ fillInputAndBlur('#custom-time-from', '2019-10-01abc');
+ fillInputAndBlur('#custom-time-to', '2019-10-10abc');
+
+ dateTimePicker.vm.$nextTick(() => {
+ expect(dateTimePicker.findAll('.invalid-feedback').length).toBe(2);
+ done();
+ });
+ });
+
+ it('keeps apply button disabled with invalid custom time range inputs', done => {
+ createComponent();
+ fillInputAndBlur('#custom-time-from', '2019-10-01abc');
+ fillInputAndBlur('#custom-time-to', '2019-09-19');
+
+ dateTimePicker.vm.$nextTick(() => {
+ expect(applyButtonElement().getAttribute('disabled')).toBe('disabled');
+ done();
+ });
+ });
+
+ it('enables apply button with valid custom time range inputs', done => {
+ createComponent();
+ fillInputAndBlur('#custom-time-from', '2019-10-01');
+ fillInputAndBlur('#custom-time-to', '2019-10-19');
+
+ dateTimePicker.vm.$nextTick(() => {
+ expect(applyButtonElement().getAttribute('disabled')).toBeNull();
+ done();
+ });
+ });
+
+ it('returns an object when apply is clicked', done => {
+ createComponent();
+ fillInputAndBlur('#custom-time-from', '2019-10-01');
+ fillInputAndBlur('#custom-time-to', '2019-10-19');
+
+ dateTimePicker.vm.$nextTick(() => {
+ jest.spyOn(dateTimePicker.vm, '$emit');
+ applyButtonElement().click();
+
+ expect(dateTimePicker.vm.$emit).toHaveBeenCalledWith('onApply', {
+ end: '2019-10-19T00:00:00Z',
+ start: '2019-10-01T00:00:00Z',
+ });
+ done();
+ });
+ });
+
+ it('hides the popover with cancel button', done => {
+ createComponent();
+ dropdownToggle().trigger('click');
+
+ dateTimePicker.vm.$nextTick(() => {
+ cancelButtonElement().click();
+
+ dateTimePicker.vm.$nextTick(() => {
+ expect(dropdownMenu().classes('show')).toBe(false);
+ done();
+ });
+ });
+ });
+});
diff --git a/spec/helpers/merge_requests_helper_spec.rb b/spec/helpers/merge_requests_helper_spec.rb
index 193390d2f2c..695d1520897 100644
--- a/spec/helpers/merge_requests_helper_spec.rb
+++ b/spec/helpers/merge_requests_helper_spec.rb
@@ -39,6 +39,7 @@ describe MergeRequestsHelper do
let(:forked_project) { fork_project(project) }
let(:merge_request) { create(:merge_request, source_project: forked_project, target_project: project) }
subject { format_mr_branch_names(merge_request) }
+
let(:source_title) { "#{forked_project.full_path}:#{merge_request.source_branch}" }
let(:target_title) { "#{project.full_path}:#{merge_request.target_branch}" }
diff --git a/spec/javascripts/monitoring/components/dashboard_spec.js b/spec/javascripts/monitoring/components/dashboard_spec.js
index 1b2b01d1c8c..75df2ce3103 100644
--- a/spec/javascripts/monitoring/components/dashboard_spec.js
+++ b/spec/javascripts/monitoring/components/dashboard_spec.js
@@ -4,7 +4,6 @@ import { GlToast } from '@gitlab/ui';
import VueDraggable from 'vuedraggable';
import MockAdapter from 'axios-mock-adapter';
import Dashboard from '~/monitoring/components/dashboard.vue';
-import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
import * as types from '~/monitoring/stores/mutation_types';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
@@ -37,6 +36,12 @@ const propsData = {
validateQueryPath: '',
};
+const resetSpy = spy => {
+ if (spy) {
+ spy.calls.reset();
+ }
+};
+
export default propsData;
describe('Dashboard', () => {
@@ -96,10 +101,15 @@ describe('Dashboard', () => {
});
describe('requests information to the server', () => {
+ let spy;
beforeEach(() => {
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
});
+ afterEach(() => {
+ resetSpy(spy);
+ });
+
it('shows up a loading state', done => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
@@ -272,7 +282,7 @@ describe('Dashboard', () => {
});
});
- it('renders the time window dropdown with a set of options', done => {
+ it('renders the datetimepicker dropdown', done => {
component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
propsData: {
@@ -282,17 +292,9 @@ describe('Dashboard', () => {
},
store,
});
- const numberOfTimeWindows = Object.keys(timeWindows).length;
setTimeout(() => {
- const timeWindowDropdown = component.$el.querySelector('.js-time-window-dropdown');
- const timeWindowDropdownEls = component.$el.querySelectorAll(
- '.js-time-window-dropdown .dropdown-item',
- );
-
- expect(timeWindowDropdown).not.toBeNull();
- expect(timeWindowDropdownEls.length).toEqual(numberOfTimeWindows);
-
+ expect(component.$el.querySelector('.js-time-window-dropdown')).not.toBeNull();
done();
});
});
@@ -355,7 +357,7 @@ describe('Dashboard', () => {
});
});
- it('defaults to the eight hours time window for non valid url parameters', done => {
+ it('shows an error message if invalid url parameters are passed', done => {
spyOnDependency(Dashboard, 'getParameterValues').and.returnValue([
'<script>alert("XSS")</script>',
]);
@@ -366,9 +368,11 @@ describe('Dashboard', () => {
store,
});
- Vue.nextTick(() => {
- expect(component.selectedTimeWindowKey).toEqual(timeWindowsKeyNames.eightHours);
+ spy = spyOn(component, 'showInvalidDateError');
+ component.$mount();
+ component.$nextTick(() => {
+ expect(component.showInvalidDateError).toHaveBeenCalled();
done();
});
});
diff --git a/spec/javascripts/monitoring/utils_spec.js b/spec/javascripts/monitoring/utils_spec.js
index 7030156931f..512dd2a0eb3 100644
--- a/spec/javascripts/monitoring/utils_spec.js
+++ b/spec/javascripts/monitoring/utils_spec.js
@@ -1,4 +1,13 @@
-import { getTimeDiff, getTimeWindow, graphDataValidatorForValues } from '~/monitoring/utils';
+import {
+ getTimeDiff,
+ getTimeWindow,
+ graphDataValidatorForValues,
+ isDateTimePickerInputValid,
+ truncateZerosInDateTime,
+ stringToISODate,
+ ISODateToString,
+ isValidDate,
+} from '~/monitoring/utils';
import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
import { graphDataPrometheusQuery, graphDataPrometheusQueryRange } from './mock_data';
@@ -57,7 +66,7 @@ describe('getTimeWindow', () => {
end: '2019-10-01T21:27:47.000Z',
},
],
- expected: timeWindowsKeyNames.eightHours,
+ expected: null,
},
{
args: [
@@ -66,7 +75,7 @@ describe('getTimeWindow', () => {
end: '',
},
],
- expected: timeWindowsKeyNames.eightHours,
+ expected: null,
},
{
args: [
@@ -75,11 +84,11 @@ describe('getTimeWindow', () => {
end: null,
},
],
- expected: timeWindowsKeyNames.eightHours,
+ expected: null,
},
{
args: [{}],
- expected: timeWindowsKeyNames.eightHours,
+ expected: null,
},
].forEach(({ args, expected }) => {
it(`returns "${expected}" with args=${JSON.stringify(args)}`, () => {
@@ -111,3 +120,190 @@ describe('graphDataValidatorForValues', () => {
expect(validGraphData).toBe(true);
});
});
+
+describe('stringToISODate', () => {
+ ['', 'null', undefined, 'abc'].forEach(input => {
+ it(`throws error for invalid input like ${input}`, done => {
+ try {
+ stringToISODate(input);
+ } catch (e) {
+ expect(e).toBeDefined();
+ done();
+ }
+ });
+ });
+ [
+ {
+ input: '2019-09-09 01:01:01',
+ output: '2019-09-09T01:01:01Z',
+ },
+ {
+ input: '2019-09-09 00:00:00',
+ output: '2019-09-09T00:00:00Z',
+ },
+ {
+ input: '2019-09-09 23:59:59',
+ output: '2019-09-09T23:59:59Z',
+ },
+ {
+ input: '2019-09-09',
+ output: '2019-09-09T00:00:00Z',
+ },
+ ].forEach(({ input, output }) => {
+ it(`returns ${output} from ${input}`, () => {
+ expect(stringToISODate(input)).toBe(output);
+ });
+ });
+});
+
+describe('ISODateToString', () => {
+ [
+ {
+ input: new Date('2019-09-09T00:00:00.000Z'),
+ output: '2019-09-09 00:00:00',
+ },
+ {
+ input: new Date('2019-09-09T07:00:00.000Z'),
+ output: '2019-09-09 07:00:00',
+ },
+ ].forEach(({ input, output }) => {
+ it(`ISODateToString return ${output} for ${input}`, () => {
+ expect(ISODateToString(input)).toBe(output);
+ });
+ });
+});
+
+describe('truncateZerosInDateTime', () => {
+ [
+ {
+ input: '',
+ output: '',
+ },
+ {
+ input: '2019-10-10',
+ output: '2019-10-10',
+ },
+ {
+ input: '2019-10-10 00:00:01',
+ output: '2019-10-10 00:00:01',
+ },
+ {
+ input: '2019-10-10 00:00:00',
+ output: '2019-10-10',
+ },
+ ].forEach(({ input, output }) => {
+ it(`truncateZerosInDateTime return ${output} for ${input}`, () => {
+ expect(truncateZerosInDateTime(input)).toBe(output);
+ });
+ });
+});
+
+describe('isValidDate', () => {
+ [
+ {
+ input: '2019-09-09T00:00:00.000Z',
+ output: true,
+ },
+ {
+ input: '2019-09-09T000:00.000Z',
+ output: false,
+ },
+ {
+ input: 'a2019-09-09T000:00.000Z',
+ output: false,
+ },
+ {
+ input: '2019-09-09T',
+ output: false,
+ },
+ {
+ input: '2019-09-09',
+ output: true,
+ },
+ {
+ input: '2019-9-9',
+ output: true,
+ },
+ {
+ input: '2019-9-',
+ output: true,
+ },
+ {
+ input: '2019--',
+ output: false,
+ },
+ {
+ input: '2019',
+ output: true,
+ },
+ {
+ input: '',
+ output: false,
+ },
+ {
+ input: null,
+ output: false,
+ },
+ ].forEach(({ input, output }) => {
+ it(`isValidDate return ${output} for ${input}`, () => {
+ expect(isValidDate(input)).toBe(output);
+ });
+ });
+});
+
+describe('isDateTimePickerInputValid', () => {
+ [
+ {
+ input: null,
+ output: false,
+ },
+ {
+ input: '',
+ output: false,
+ },
+ {
+ input: 'xxxx-xx-xx',
+ output: false,
+ },
+ {
+ input: '9999-99-19',
+ output: false,
+ },
+ {
+ input: '2019-19-23',
+ output: false,
+ },
+ {
+ input: '2019-09-23',
+ output: true,
+ },
+ {
+ input: '2019-09-23 x',
+ output: false,
+ },
+ {
+ input: '2019-09-29 0:0:0',
+ output: false,
+ },
+ {
+ input: '2019-09-29 00:00:00',
+ output: true,
+ },
+ {
+ input: '2019-09-29 24:24:24',
+ output: false,
+ },
+ {
+ input: '2019-09-29 23:24:24',
+ output: true,
+ },
+ {
+ input: '2019-09-29 23:24:24 ',
+ output: false,
+ },
+ ].forEach(({ input, output }) => {
+ it(`returns ${output} for ${input}`, () => {
+ expect(isDateTimePickerInputValid(input)).toBe(output);
+ });
+ });
+});
diff --git a/spec/lib/backup/files_spec.rb b/spec/lib/backup/files_spec.rb
index e903eada62d..b75f3bafeef 100644
--- a/spec/lib/backup/files_spec.rb
+++ b/spec/lib/backup/files_spec.rb
@@ -24,6 +24,7 @@ describe Backup::Files do
describe '#restore' do
subject { described_class.new('registry', '/var/gitlab-registry') }
+
let(:timestamp) { Time.utc(2017, 3, 22) }
around do |example|
diff --git a/spec/lib/banzai/filter/project_reference_filter_spec.rb b/spec/lib/banzai/filter/project_reference_filter_spec.rb
index 927d226c400..d0b4542d503 100644
--- a/spec/lib/banzai/filter/project_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/project_reference_filter_spec.rb
@@ -15,6 +15,7 @@ describe Banzai::Filter::ProjectReferenceFilter do
let(:project) { create(:project, :public) }
subject { project }
+
let(:subject_name) { "project" }
let(:reference) { get_reference(project) }
diff --git a/spec/lib/banzai/filter/user_reference_filter_spec.rb b/spec/lib/banzai/filter/user_reference_filter_spec.rb
index 6bc87d245f5..a09aeb7d7f6 100644
--- a/spec/lib/banzai/filter/user_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/user_reference_filter_spec.rb
@@ -12,6 +12,7 @@ describe Banzai::Filter::UserReferenceFilter do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
subject { user }
+
let(:subject_name) { "user" }
let(:reference) { get_reference(user) }
diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
index b44ae67e430..eac1cf16a8f 100644
--- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb
@@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::CommitParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
index da853233018..78b337466aa 100644
--- a/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/commit_range_parser_spec.rb
@@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::CommitRangeParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
index 0f29a95bdcc..9343d52e44b 100644
--- a/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/external_issue_parser_spec.rb
@@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::ExternalIssueParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/label_parser_spec.rb b/spec/lib/banzai/reference_parser/label_parser_spec.rb
index cf8adb57ffc..8b66a891e69 100644
--- a/spec/lib/banzai/reference_parser/label_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/label_parser_spec.rb
@@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::LabelParser do
let(:user) { create(:user) }
let(:label) { create(:label, project: project) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
index 1561dabcdbf..cb65893aea0 100644
--- a/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/merge_request_parser_spec.rb
@@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::MergeRequestParser do
let(:project) { create(:project, :public) }
let(:merge_request) { create(:merge_request, source_project: project) }
subject { described_class.new(Banzai::RenderContext.new(merge_request.target_project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
index 006f8e37690..25ba41dd8a0 100644
--- a/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/milestone_parser_spec.rb
@@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::MilestoneParser do
let(:user) { create(:user) }
let(:milestone) { create(:milestone, project: project) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#nodes_visible_to_user' do
diff --git a/spec/lib/banzai/reference_parser/project_parser_spec.rb b/spec/lib/banzai/reference_parser/project_parser_spec.rb
index e4936aa9e57..356dde1e9c2 100644
--- a/spec/lib/banzai/reference_parser/project_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/project_parser_spec.rb
@@ -8,6 +8,7 @@ describe Banzai::ReferenceParser::ProjectParser do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#referenced_by' do
diff --git a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
index 528f79ed020..05dc1cb4d2d 100644
--- a/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/snippet_parser_spec.rb
@@ -12,6 +12,7 @@ describe Banzai::ReferenceParser::SnippetParser do
let(:project_member) { create(:user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
def visible_references(snippet_visibility, user = nil)
diff --git a/spec/lib/banzai/reference_parser/user_parser_spec.rb b/spec/lib/banzai/reference_parser/user_parser_spec.rb
index a5b4e59a3a1..931fb1e3953 100644
--- a/spec/lib/banzai/reference_parser/user_parser_spec.rb
+++ b/spec/lib/banzai/reference_parser/user_parser_spec.rb
@@ -9,6 +9,7 @@ describe Banzai::ReferenceParser::UserParser do
let(:user) { create(:user) }
let(:project) { create(:project, :public, group: group, creator: user) }
subject { described_class.new(Banzai::RenderContext.new(project, user)) }
+
let(:link) { empty_html_link }
describe '#referenced_by' do
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 3d0d3f91859..7f7a285c453 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -308,8 +308,8 @@ describe Gitlab::BitbucketImport::Importer do
importer.execute
- expect(project.issues.where(state: "closed").size).to eq(5)
- expect(project.issues.where(state: "opened").size).to eq(2)
+ expect(project.issues.where(state_id: Issue.available_states[:closed]).size).to eq(5)
+ expect(project.issues.where(state_id: Issue.available_states[:opened]).size).to eq(2)
end
describe 'wiki import' do
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
index 24d17eb0fb3..73c3cad88bc 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata/entry_spec.rb
@@ -34,27 +34,32 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe '#basename' do
subject { |example| path(example).basename }
+
it { is_expected.to eq 'absolute_path' }
end
end
describe 'path/dir_1/', path: 'path/dir_1/' do
subject { |example| path(example) }
+
it { is_expected.to have_parent }
it { is_expected.to be_directory }
describe '#basename' do
subject { |example| path(example).basename }
+
it { is_expected.to eq 'dir_1/' }
end
describe '#name' do
subject { |example| path(example).name }
+
it { is_expected.to eq 'dir_1' }
end
describe '#parent' do
subject { |example| path(example).parent }
+
it { is_expected.to eq entry('path/') }
end
@@ -102,21 +107,25 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe '#nodes' do
subject { |example| path(example).nodes }
+
it { is_expected.to eq 2 }
end
describe '#exists?' do
subject { |example| path(example).exists? }
+
it { is_expected.to be true }
end
describe '#empty?' do
subject { |example| path(example).empty? }
+
it { is_expected.to be false }
end
describe '#total_size' do
subject { |example| path(example).total_size }
+
it { is_expected.to eq(30) }
end
end
@@ -124,10 +133,12 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe 'empty path', path: '' do
subject { |example| path(example) }
+
it { is_expected.not_to have_parent }
describe '#children' do
subject { |example| path(example).children }
+
it { expect(subject.count).to eq 3 }
end
end
@@ -135,6 +146,7 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe 'path/dir_1/subdir/subfile', path: 'path/dir_1/subdir/subfile' do
describe '#nodes' do
subject { |example| path(example).nodes }
+
it { is_expected.to eq 4 }
end
@@ -153,11 +165,13 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe 'non-existent/', path: 'non-existent/' do
describe '#empty?' do
subject { |example| path(example).empty? }
+
it { is_expected.to be true }
end
describe '#exists?' do
subject { |example| path(example).exists? }
+
it { is_expected.to be false }
end
end
@@ -165,6 +179,7 @@ describe Gitlab::Ci::Build::Artifacts::Metadata::Entry do
describe 'another_directory/', path: 'another_directory/' do
describe '#empty?' do
subject { |example| path(example).empty? }
+
it { is_expected.to be true }
end
end
diff --git a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
index ff189c4701e..bfa65c66b33 100644
--- a/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
+++ b/spec/lib/gitlab/ci/build/artifacts/metadata_spec.rb
@@ -76,21 +76,25 @@ describe Gitlab::Ci::Build::Artifacts::Metadata do
describe '#to_entry' do
subject { metadata('').to_entry }
+
it { is_expected.to be_an_instance_of(Gitlab::Ci::Build::Artifacts::Metadata::Entry) }
end
describe '#full_version' do
subject { metadata('').full_version }
+
it { is_expected.to eq 'GitLab Build Artifacts Metadata 0.0.1' }
end
describe '#version' do
subject { metadata('').version }
+
it { is_expected.to eq '0.0.1' }
end
describe '#errors' do
subject { metadata('').errors }
+
it { is_expected.to eq({}) }
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/cache_spec.rb b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
index 4cb63168ec7..9aab3664e1c 100644
--- a/spec/lib/gitlab/ci/config/entry/cache_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/cache_spec.rb
@@ -69,6 +69,7 @@ describe Gitlab::Ci::Config::Entry::Cache do
context 'when entry value is not correct' do
describe '#errors' do
subject { entry.errors }
+
context 'when is not a hash' do
let(:config) { 'ls' }
diff --git a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
index 48d0864cfca..877e3ec6216 100644
--- a/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/coverage_spec.rb
@@ -11,11 +11,13 @@ describe Gitlab::Ci::Config::Entry::Coverage do
describe '#errors' do
subject { entry.errors }
+
it { is_expected.to include(/coverage config must be a regular expression/) }
end
describe '#valid?' do
subject { entry }
+
it { is_expected.not_to be_valid }
end
end
@@ -25,16 +27,19 @@ describe Gitlab::Ci::Config::Entry::Coverage do
describe '#value' do
subject { entry.value }
+
it { is_expected.to eq(config[1...-1]) }
end
describe '#errors' do
subject { entry.errors }
+
it { is_expected.to be_empty }
end
describe '#valid?' do
subject { entry }
+
it { is_expected.to be_valid }
end
end
@@ -44,11 +49,13 @@ describe Gitlab::Ci::Config::Entry::Coverage do
describe '#errors' do
subject { entry.errors }
+
it { is_expected.to include(/coverage config must be a regular expression/) }
end
describe '#valid?' do
subject { entry }
+
it { is_expected.not_to be_valid }
end
end
diff --git a/spec/lib/gitlab/cleanup/project_uploads_spec.rb b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
index 7bad788e44e..5787cce7d20 100644
--- a/spec/lib/gitlab/cleanup/project_uploads_spec.rb
+++ b/spec/lib/gitlab/cleanup/project_uploads_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::Cleanup::ProjectUploads do
subject { described_class.new(logger: logger) }
+
let(:logger) { double(:logger) }
before do
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
index 1e9cf0017be..bd1c2b10dc8 100644
--- a/spec/lib/gitlab/danger/teammate_spec.rb
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -8,6 +8,7 @@ require 'gitlab/danger/teammate'
describe Gitlab::Danger::Teammate do
subject { described_class.new(options.stringify_keys) }
+
let(:options) { { username: 'luigi', projects: projects, role: role } }
let(:projects) { { project => capabilities } }
let(:role) { 'Engineer, Manage' }
diff --git a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
index 6ef1e41450f..a13727b62ea 100644
--- a/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
+++ b/spec/lib/gitlab/discussions_diff/file_collection_spec.rb
@@ -40,6 +40,14 @@ describe Gitlab::DiscussionsDiff::FileCollection do
subject.load_highlight
end
+ it 'does not write cache for empty mapping' do
+ allow(subject).to receive(:highlighted_lines_by_ids).and_return([])
+
+ expect(Gitlab::DiscussionsDiff::HighlightCache).not_to receive(:write_multiple)
+
+ subject.load_highlight
+ end
+
it 'does not write cache for resolved notes' do
diff_note_a.update_column(:resolved_at, Time.now)
diff --git a/spec/lib/gitlab/downtime_check_spec.rb b/spec/lib/gitlab/downtime_check_spec.rb
index 56ad49d528f..5a5e34961a4 100644
--- a/spec/lib/gitlab/downtime_check_spec.rb
+++ b/spec/lib/gitlab/downtime_check_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::DowntimeCheck do
subject { described_class.new }
+
let(:path) { 'foo.rb' }
describe '#check' do
diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb
index 84c5b38127e..b57764bceef 100644
--- a/spec/lib/gitlab/email/message/repository_push_spec.rb
+++ b/spec/lib/gitlab/email/message/repository_push_spec.rb
@@ -28,90 +28,107 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#project' do
subject { message.project }
+
it { is_expected.to eq project }
it { is_expected.to be_an_instance_of Project }
end
describe '#project_namespace' do
subject { message.project_namespace }
+
it { is_expected.to eq group }
it { is_expected.to be_kind_of Namespace }
end
describe '#project_name_with_namespace' do
subject { message.project_name_with_namespace }
+
it { is_expected.to eq "#{group.name} / #{project.path}" }
end
describe '#author' do
subject { message.author }
+
it { is_expected.to eq author }
it { is_expected.to be_an_instance_of User }
end
describe '#author_name' do
subject { message.author_name }
+
it { is_expected.to eq 'Author' }
end
describe '#commits' do
subject { message.commits }
+
it { is_expected.to be_kind_of Array }
it { is_expected.to all(be_instance_of Commit) }
end
describe '#diffs' do
subject { message.diffs }
+
it { is_expected.to all(be_an_instance_of Gitlab::Diff::File) }
end
describe '#diffs_count' do
subject { message.diffs_count }
+
it { is_expected.to eq raw_compare.diffs.size }
end
describe '#compare' do
subject { message.compare }
+
it { is_expected.to be_an_instance_of Compare }
end
describe '#compare_timeout' do
subject { message.compare_timeout }
+
it { is_expected.to eq raw_compare.diffs.overflow? }
end
describe '#reverse_compare?' do
subject { message.reverse_compare? }
+
it { is_expected.to eq false }
end
describe '#disable_diffs?' do
subject { message.disable_diffs? }
+
it { is_expected.to eq false }
end
describe '#send_from_committer_email?' do
subject { message.send_from_committer_email? }
+
it { is_expected.to eq true }
end
describe '#action_name' do
subject { message.action_name }
+
it { is_expected.to eq 'pushed to' }
end
describe '#ref_name' do
subject { message.ref_name }
+
it { is_expected.to eq 'master' }
end
describe '#ref_type' do
subject { message.ref_type }
+
it { is_expected.to eq 'branch' }
end
describe '#target_url' do
subject { message.target_url }
+
it { is_expected.to include 'compare' }
it { is_expected.to include compare.commits.first.parents.first.id }
it { is_expected.to include compare.commits.last.id }
@@ -119,6 +136,7 @@ describe Gitlab::Email::Message::RepositoryPush do
describe '#subject' do
subject { message.subject }
+
it { is_expected.to include "[Git][#{project.full_path}]" }
it { is_expected.to include "#{compare.commits.length} commits" }
it { is_expected.to include compare.commits.first.message.split("\n").first }
diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb
index 4d473731f39..2e5fd16d370 100644
--- a/spec/lib/gitlab/experimentation_spec.rb
+++ b/spec/lib/gitlab/experimentation_spec.rb
@@ -75,6 +75,7 @@ describe Gitlab::Experimentation do
describe '.enabled?' do
subject { described_class.enabled?(:test_experiment, experimentation_subject_index) }
+
let(:experimentation_subject_index) { 9 }
context 'feature toggle is enabled, we are on the right environment and we are selected' do
diff --git a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
index d24f5c45107..eef3b9de476 100644
--- a/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
+++ b/spec/lib/gitlab/gfm/uploads_rewriter_spec.rb
@@ -84,11 +84,13 @@ describe Gitlab::Gfm::UploadsRewriter do
describe '#needs_rewrite?' do
subject { rewriter.needs_rewrite? }
+
it { is_expected.to eq true }
end
describe '#files' do
subject { rewriter.files }
+
it { is_expected.to be_an(Array) }
end
end
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 0764e525ede..02ef7b92538 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -44,6 +44,7 @@ describe Gitlab::Git::Branch, :seed_helper do
describe '#size' do
subject { super().size }
+
it { is_expected.to eq(SeedRepo::Repo::BRANCHES.size) }
end
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index 3f0e6b34291..23651e3d7f2 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -174,6 +174,7 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#id' do
subject { super().id }
+
it { is_expected.to eq(SeedRepo::LastCommit::ID) }
end
end
@@ -183,6 +184,7 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#id' do
subject { super().id }
+
it { is_expected.to eq(SeedRepo::Commit::ID) }
end
end
@@ -192,6 +194,7 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#id' do
subject { super().id }
+
it { is_expected.to eq(SeedRepo::BigCommit::ID) }
end
end
@@ -544,11 +547,13 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#id' do
subject { super().id }
+
it { is_expected.to eq(sample_commit_hash[:id])}
end
describe '#message' do
subject { super().message }
+
it { is_expected.to eq(sample_commit_hash[:message])}
end
end
@@ -558,16 +563,19 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#additions' do
subject { super().additions }
+
it { is_expected.to eq(11) }
end
describe '#deletions' do
subject { super().deletions }
+
it { is_expected.to eq(6) }
end
describe '#total' do
subject { super().total }
+
it { is_expected.to eq(17) }
end
end
@@ -596,6 +604,7 @@ describe Gitlab::Git::Commit, :seed_helper do
describe '#keys' do
subject { super().keys.sort }
+
it { is_expected.to match(sample_commit_hash.keys.sort) }
end
end
diff --git a/spec/lib/gitlab/git/diff_collection_spec.rb b/spec/lib/gitlab/git/diff_collection_spec.rb
index ded173c49ef..ce45d6e24ba 100644
--- a/spec/lib/gitlab/git/diff_collection_spec.rb
+++ b/spec/lib/gitlab/git/diff_collection_spec.rb
@@ -10,6 +10,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
expanded: expanded
)
end
+
let(:iterator) { MutatingConstantIterator.new(file_count, fake_diff(line_length, line_count)) }
let(:file_count) { 0 }
let(:line_length) { 1 }
@@ -21,6 +22,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#to_a' do
subject { super().to_a }
+
it { is_expected.to be_kind_of ::Array }
end
@@ -52,16 +54,19 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('3') }
end
@@ -76,6 +81,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -84,16 +90,19 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('3') }
end
@@ -108,6 +117,7 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
end
@@ -118,21 +128,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_truthy }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('0+') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq 1000 }
end
@@ -143,21 +157,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('3') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -174,21 +192,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_truthy }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('10+') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq 10 }
end
@@ -199,21 +221,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('11') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -226,21 +252,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_truthy }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('3+') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq 120 }
end
@@ -251,21 +281,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('11') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -282,21 +316,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('10') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -310,21 +348,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_truthy }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('9+') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -335,21 +377,25 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('10') }
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq file_count * line_count }
end
@@ -363,26 +409,31 @@ describe Gitlab::Git::DiffCollection, :seed_helper do
describe '#overflow?' do
subject { super().overflow? }
+
it { is_expected.to be_falsey }
end
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_truthy }
end
describe '#size' do
subject { super().size }
+
it { is_expected.to eq(0) }
end
describe '#real_size' do
subject { super().real_size }
+
it { is_expected.to eq('0')}
end
describe '#line_count' do
subject { super().line_count }
+
it { is_expected.to eq 0 }
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 04a648a0da0..44c41da7560 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -93,6 +93,7 @@ describe Gitlab::Git::Repository, :seed_helper do
describe '#last' do
subject { super().last }
+
it { is_expected.to eq("v1.2.1") }
end
it { is_expected.to include("v1.0.0") }
@@ -215,11 +216,13 @@ describe Gitlab::Git::Repository, :seed_helper do
describe '#first' do
subject { super().first }
+
it { is_expected.to eq('feature') }
end
describe '#last' do
subject { super().last }
+
it { is_expected.to eq('v1.2.1') }
end
end
diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb
index 22ad88e28cb..0f1745fcc02 100644
--- a/spec/lib/gitlab/gitlab_import/client_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/client_spec.rb
@@ -52,6 +52,7 @@ describe Gitlab::GitlabImport::Client do
describe '#projects' do
subject(:method) { :projects }
+
let(:args) { [] }
let(:element_list) { build_list(:project, 2) }
@@ -67,6 +68,7 @@ describe Gitlab::GitlabImport::Client do
describe '#issues' do
subject(:method) { :issues }
+
let(:args) { [1] }
let(:element_list) { build_list(:issue, 2) }
@@ -82,6 +84,7 @@ describe Gitlab::GitlabImport::Client do
describe '#issue_comments' do
subject(:method) { :issue_comments }
+
let(:args) { [1, 1] }
let(:element_list) { build_list(:note_on_issue, 2) }
diff --git a/spec/lib/gitlab/graphs/commits_spec.rb b/spec/lib/gitlab/graphs/commits_spec.rb
index 530d4a981bf..09654e0439e 100644
--- a/spec/lib/gitlab/graphs/commits_spec.rb
+++ b/spec/lib/gitlab/graphs/commits_spec.rb
@@ -11,12 +11,14 @@ describe Gitlab::Graphs::Commits do
describe '#commit_per_day' do
context 'when range is only commits from today' do
subject { described_class.new([commit2, commit1]).commit_per_day }
+
it { is_expected.to eq 2 }
end
end
context 'when range is only commits from today' do
subject { described_class.new([commit2, commit1]) }
+
describe '#commit_per_day' do
it { expect(subject.commit_per_day).to eq 2 }
end
@@ -28,6 +30,7 @@ describe Gitlab::Graphs::Commits do
context 'with commits from yesterday and today' do
subject { described_class.new([commit2, commit1_yesterday]) }
+
describe '#commit_per_day' do
it { expect(subject.commit_per_day).to eq 1.0 }
end
diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
index 99d10312c15..36e2fd04aeb 100644
--- a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
+++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
@@ -30,6 +30,7 @@ describe Gitlab::HealthChecks::GitalyCheck do
describe '#metrics' do
subject { described_class.metrics }
+
let(:server) { double(storage: 'default', read_writeable?: up) }
before do
diff --git a/spec/lib/gitlab/health_checks/simple_check_shared.rb b/spec/lib/gitlab/health_checks/simple_check_shared.rb
index c3d55a11909..03a7cf249cf 100644
--- a/spec/lib/gitlab/health_checks/simple_check_shared.rb
+++ b/spec/lib/gitlab/health_checks/simple_check_shared.rb
@@ -1,6 +1,7 @@
shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
describe '#metrics' do
subject { described_class.metrics }
+
context 'Check is passing' do
before do
allow(described_class).to receive(:check).and_return success_result
@@ -34,6 +35,7 @@ shared_context 'simple_check' do |metrics_prefix, check_name, success_result|
describe '#readiness' do
subject { described_class.readiness }
+
context 'Check returns ok' do
before do
allow(described_class).to receive(:check).and_return success_result
diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
index 6013fb78bc7..ebd7feb0055 100644
--- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb
+++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb
@@ -26,7 +26,7 @@ describe Gitlab::HookData::IssueBuilder do
duplicated_to_id
project_id
relative_position
- state
+ state_id
time_estimate
title
updated_at
@@ -41,6 +41,7 @@ describe Gitlab::HookData::IssueBuilder do
expect(data).to include(:human_time_estimate)
expect(data).to include(:human_total_time_spent)
expect(data).to include(:assignee_ids)
+ expect(data).to include(:state)
expect(data).to include('labels' => [label.hook_attrs])
end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 1efd7bf5c71..4fd61383c6b 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -25,6 +25,7 @@ issues:
- epic
- designs
- design_versions
+- description_versions
- prometheus_alerts
- prometheus_alert_events
- self_managed_prometheus_alert_events
@@ -132,6 +133,7 @@ merge_requests:
- blocks_as_blockee
- blocking_merge_requests
- blocked_merge_requests
+- description_versions
external_pull_requests:
- project
merge_request_diff:
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index 218031784cb..676973ff5e7 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -434,6 +434,11 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
labels: 0,
milestones: 0,
first_issue_labels: 1
+
+ it 'restores issue states' do
+ expect(project.issues.with_state(:closed).count).to eq(1)
+ expect(project.issues.with_state(:opened).count).to eq(1)
+ end
end
context 'with existing group models' do
diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb
index 51b2fd06b46..a23e68a8f00 100644
--- a/spec/lib/gitlab/import_export/relation_factory_spec.rb
+++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb
@@ -85,7 +85,7 @@ describe Gitlab::ImportExport::RelationFactory do
class FooModel
include ActiveModel::Model
- def initialize(params)
+ def initialize(params = {})
params.each { |key, value| send("#{key}=", value) }
end
diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
index b8add3c1324..1097d26c320 100644
--- a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::Metrics::Samplers::PumaSampler do
subject { described_class.new(5) }
+
let(:null_metric) { double('null_metric', set: nil, observe: nil) }
before do
diff --git a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
index b6f2524a9d0..51514dd0ffd 100644
--- a/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
+++ b/spec/lib/gitlab/phabricator_import/worker_state_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::PhabricatorImport::WorkerState, :clean_gitlab_redis_shared_state do
subject(:state) { described_class.new('weird-project-id') }
+
let(:key) { 'phabricator-import/jobs/project-weird-project-id/job-count' }
describe '#add_job' do
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index f6ace0d8bf5..6bc9b6365d1 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -259,6 +259,7 @@ describe Gitlab::ReferenceExtractor do
describe '.references_pattern' do
subject { described_class.references_pattern }
+
it { is_expected.to be_kind_of Regexp }
end
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index a744f48da1f..cde12d4b310 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
describe Gitlab::RequestContext do
describe '#client_ip' do
subject { described_class.client_ip }
+
let(:app) { -> (env) {} }
let(:env) { Hash.new }
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index ff9e31ec346..a17e9a31212 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -396,6 +396,7 @@ describe Gitlab::Shell do
describe 'namespace actions' do
subject { described_class.new }
+
let(:storage) { Gitlab.config.repositories.storages.keys.first }
describe '#add_namespace' do
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
index bf3bc8e1add..b5be43ec96c 100644
--- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Gitlab::SidekiqMiddleware::MemoryKiller do
subject { described_class.new }
+
let(:pid) { 999 }
let(:worker) { double(:worker, class: ProjectCacheWorker) }
diff --git a/spec/lib/gitlab/utils/override_spec.rb b/spec/lib/gitlab/utils/override_spec.rb
index 5855c4374a9..e2776efac85 100644
--- a/spec/lib/gitlab/utils/override_spec.rb
+++ b/spec/lib/gitlab/utils/override_spec.rb
@@ -151,6 +151,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is prepending it' do
subject { extension }
+
let(:klass) { prepending_class }
it_behaves_like 'checking as intended'
@@ -158,6 +159,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is including it' do
subject { extension }
+
let(:klass) { including_class }
it_behaves_like 'checking as intended, nothing was overridden'
@@ -177,6 +179,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is prepending it' do
subject { extension }
+
let(:klass) { prepending_class }
it_behaves_like 'nothing happened'
@@ -184,6 +187,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is including it' do
subject { extension }
+
let(:klass) { including_class }
it 'does not complain when it is overriding something' do
@@ -215,6 +219,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is prepending it' do
subject { extension }
+
let(:klass) { prepending_class_methods }
it_behaves_like 'checking as intended'
@@ -222,6 +227,7 @@ describe Gitlab::Utils::Override do
context 'when subject is a module, and class is extending it' do
subject { extension }
+
let(:klass) { extending_class_methods }
it_behaves_like 'checking as intended, nothing was overridden'
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index 91b076c31d6..0f7f57095df 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -54,6 +54,7 @@ describe GoogleApi::CloudPlatform::Client do
describe '#projects_zones_clusters_get' do
subject { client.projects_zones_clusters_get(spy, spy, spy) }
+
let(:gke_cluster) { double }
before do
@@ -160,6 +161,7 @@ describe GoogleApi::CloudPlatform::Client do
describe '#projects_zones_operations' do
subject { client.projects_zones_operations(spy, spy, spy) }
+
let(:operation) { double }
before do
diff --git a/spec/lib/json_web_token/token_spec.rb b/spec/lib/json_web_token/token_spec.rb
index 916d11ce0ed..ca587a6ebcd 100644
--- a/spec/lib/json_web_token/token_spec.rb
+++ b/spec/lib/json_web_token/token_spec.rb
@@ -16,6 +16,7 @@ describe JSONWebToken::Token do
context 'embeds default payload' do
subject { token.payload }
+
let(:default) { token.send(:default_payload) }
it { is_expected.to include(default) }
diff --git a/spec/lib/omni_auth/strategies/jwt_spec.rb b/spec/lib/omni_auth/strategies/jwt_spec.rb
index bdf3ea6be98..a8c565aa705 100644
--- a/spec/lib/omni_auth/strategies/jwt_spec.rb
+++ b/spec/lib/omni_auth/strategies/jwt_spec.rb
@@ -8,6 +8,7 @@ describe OmniAuth::Strategies::Jwt do
context '#decoded' do
subject { described_class.new({}) }
+
let(:timestamp) { Time.now.to_i }
let(:jwt_config) { Devise.omniauth_configs[:jwt] }
let(:claims) do
diff --git a/spec/lib/uploaded_file_spec.rb b/spec/lib/uploaded_file_spec.rb
index 2cb4727bd4b..2bbbd67b13c 100644
--- a/spec/lib/uploaded_file_spec.rb
+++ b/spec/lib/uploaded_file_spec.rb
@@ -72,16 +72,6 @@ describe UploadedFile do
end
end
- context 'when only remote id is specified' do
- let(:params) do
- { 'file.remote_id' => 'remote_id' }
- end
-
- it "raises an error" do
- expect { subject }.to raise_error(UploadedFile::InvalidPathError, /file is invalid/)
- end
- end
-
context 'when verifying allowed paths' do
let(:params) do
{ 'file.path' => temp_file.path }
@@ -120,6 +110,52 @@ describe UploadedFile do
end
end
+ describe '.initialize' do
+ context 'when no size is provided' do
+ it 'determine size from local path' do
+ file = described_class.new(temp_file.path)
+
+ expect(file.size).to eq(temp_file.size)
+ end
+
+ it 'raises an exception if is a remote file' do
+ expect do
+ described_class.new(nil, remote_id: 'id')
+ end.to raise_error(UploadedFile::UnknownSizeError, 'Unable to determine file size')
+ end
+ end
+
+ context 'when size is a number' do
+ let_it_be(:size) { 1.gigabyte }
+
+ it 'is overridden by the size of the local file' do
+ file = described_class.new(temp_file.path, size: size)
+
+ expect(file.size).to eq(temp_file.size)
+ end
+
+ it 'is respected if is a remote file' do
+ file = described_class.new(nil, remote_id: 'id', size: size)
+
+ expect(file.size).to eq(size)
+ end
+ end
+
+ context 'when size is a string' do
+ it 'is converted to a number' do
+ file = described_class.new(nil, remote_id: 'id', size: '1')
+
+ expect(file.size).to eq(1)
+ end
+
+ it 'raises an exception if does not represent a number' do
+ expect do
+ described_class.new(nil, remote_id: 'id', size: 'not a number')
+ end.to raise_error(UploadedFile::UnknownSizeError, 'Unable to determine file size')
+ end
+ end
+ end
+
describe '#sanitize_filename' do
it { expect(described_class.new(temp_file.path).sanitize_filename('spaced name')).to eq('spaced_name') }
it { expect(described_class.new(temp_file.path).sanitize_filename('#$%^&')).to eq('_____') }
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index da8ea00b2f6..058305bc04e 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -574,6 +574,7 @@ describe Ci::Build do
describe '#artifacts_metadata?' do
subject { build.artifacts_metadata? }
+
context 'artifacts metadata does not exist' do
it { is_expected.to be_falsy }
end
@@ -586,6 +587,7 @@ describe Ci::Build do
describe '#artifacts_expire_in' do
subject { build.artifacts_expire_in }
+
it { is_expected.to be_nil }
context 'when artifacts_expire_at is specified' do
@@ -1265,6 +1267,7 @@ describe Ci::Build do
describe '#erasable?' do
subject { build.erasable? }
+
it { is_expected.to be_truthy }
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 70ff3cf5dc4..ac438f7d473 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -686,11 +686,13 @@ describe Ci::Runner do
describe '#has_tags?' do
context 'when runner has tags' do
subject { create(:ci_runner, tag_list: ['tag']) }
+
it { is_expected.to have_tags }
end
context 'when runner does not have tags' do
subject { create(:ci_runner, tag_list: []) }
+
it { is_expected.not_to have_tags }
end
end
diff --git a/spec/models/concerns/noteable_spec.rb b/spec/models/concerns/noteable_spec.rb
index 929b5f52c7c..f823ac0165f 100644
--- a/spec/models/concerns/noteable_spec.rb
+++ b/spec/models/concerns/noteable_spec.rb
@@ -6,6 +6,7 @@ describe Noteable do
let!(:active_diff_note1) { create(:diff_note_on_merge_request) }
let(:project) { active_diff_note1.project }
subject { active_diff_note1.noteable }
+
let!(:active_diff_note2) { create(:diff_note_on_merge_request, project: project, noteable: subject, in_reply_to: active_diff_note1) }
let!(:active_diff_note3) { create(:diff_note_on_merge_request, project: project, noteable: subject, position: active_position2) }
let!(:outdated_diff_note1) { create(:diff_note_on_merge_request, project: project, noteable: subject, position: outdated_position) }
diff --git a/spec/models/concerns/token_authenticatable_spec.rb b/spec/models/concerns/token_authenticatable_spec.rb
index 51e28974ae0..43b894b5957 100644
--- a/spec/models/concerns/token_authenticatable_spec.rb
+++ b/spec/models/concerns/token_authenticatable_spec.rb
@@ -17,6 +17,7 @@ describe User, 'TokenAuthenticatable' do
describe 'ensures authentication token' do
subject { create(:user).send(token_field) }
+
it { is_expected.to be_a String }
end
end
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index c137444763b..1dbae78a01d 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -16,6 +16,7 @@ describe DeployKeysProject do
describe "Destroying" do
let(:project) { create(:project) }
subject { create(:deploy_keys_project, project: project) }
+
let(:deploy_key) { subject.deploy_key }
context "when the deploy key is only used by this project" do
diff --git a/spec/models/description_version_spec.rb b/spec/models/description_version_spec.rb
new file mode 100644
index 00000000000..5ec34c0cde4
--- /dev/null
+++ b/spec/models/description_version_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DescriptionVersion do
+ describe 'associations' do
+ it { is_expected.to belong_to :issue }
+ it { is_expected.to belong_to :merge_request }
+ end
+
+ describe 'validations' do
+ describe 'exactly_one_issuable' do
+ using RSpec::Parameterized::TableSyntax
+
+ subject { described_class.new(issue_id: issue_id, merge_request_id: merge_request_id).valid? }
+
+ where(:issue_id, :merge_request_id, :valid?) do
+ nil | 1 | true
+ 1 | nil | true
+ nil | nil | false
+ 1 | 1 | false
+ end
+
+ with_them do
+ it { is_expected.to eq(valid?) }
+ end
+ end
+ end
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 62663c247d1..ff2e1aa047e 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -100,26 +100,31 @@ describe Event do
describe '#membership_changed?' do
context "created" do
subject { build(:event, :created).membership_changed? }
+
it { is_expected.to be_falsey }
end
context "updated" do
subject { build(:event, :updated).membership_changed? }
+
it { is_expected.to be_falsey }
end
context "expired" do
subject { build(:event, :expired).membership_changed? }
+
it { is_expected.to be_truthy }
end
context "left" do
subject { build(:event, :left).membership_changed? }
+
it { is_expected.to be_truthy }
end
context "joined" do
subject { build(:event, :joined).membership_changed? }
+
it { is_expected.to be_truthy }
end
end
diff --git a/spec/models/gpg_signature_spec.rb b/spec/models/gpg_signature_spec.rb
index dd18c8842ab..a780b8bfdf5 100644
--- a/spec/models/gpg_signature_spec.rb
+++ b/spec/models/gpg_signature_spec.rb
@@ -20,6 +20,7 @@ RSpec.describe GpgSignature do
describe 'validation' do
subject { described_class.new }
+
it { is_expected.to validate_presence_of(:commit_sha) }
it { is_expected.to validate_presence_of(:project_id) }
it { is_expected.to validate_presence_of(:gpg_key_primary_keyid) }
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 9c58d307c4c..18a1a30eee5 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -138,7 +138,10 @@ describe Issue do
end
it 'changes the state to closed' do
- expect { issue.close }.to change { issue.state }.from('opened').to('closed')
+ open_state = described_class.available_states[:opened]
+ closed_state = described_class.available_states[:closed]
+
+ expect { issue.close }.to change { issue.state_id }.from(open_state).to(closed_state)
end
end
@@ -155,7 +158,7 @@ describe Issue do
end
it 'changes the state to opened' do
- expect { issue.reopen }.to change { issue.state }.from('closed').to('opened')
+ expect { issue.reopen }.to change { issue.state_id }.from(described_class.available_states[:closed]).to(described_class.available_states[:opened])
end
end
@@ -277,6 +280,7 @@ describe Issue do
context 'checking destination project also' do
subject { issue.can_move?(user, to_project) }
+
let(:to_project) { create(:project) }
context 'destination project allowed' do
@@ -899,4 +903,6 @@ describe Issue do
let(:default_params) { { project: project } }
end
end
+
+ it_behaves_like 'versioned description'
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index b146c767f82..ad79bee8801 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -470,7 +470,7 @@ describe MergeRequest do
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
allow(subject).to receive(:commits).and_return([commit])
- allow(subject).to receive(:state).and_return("closed")
+ allow(subject).to receive(:state_id).and_return(described_class.available_states[:closed])
expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
end
@@ -479,7 +479,7 @@ describe MergeRequest do
issue = create :issue, project: subject.project
commit = double('commit1', safe_message: "Fixes #{issue.to_reference}")
allow(subject).to receive(:commits).and_return([commit])
- allow(subject).to receive(:state).and_return("merged")
+ allow(subject).to receive(:state_id).and_return(described_class.available_states[:merged])
expect { subject.cache_merge_request_closes_issues!(subject.author) }.not_to change(subject.merge_requests_closing_issues, :count)
end
@@ -541,6 +541,7 @@ describe MergeRequest do
context 'with diffs' do
subject { create(:merge_request, :with_diffs) }
+
it 'returns the sha of the source branch last commit' do
expect(subject.source_branch_sha).to eq(last_branch_commit.sha)
end
@@ -548,6 +549,7 @@ describe MergeRequest do
context 'without diffs' do
subject { create(:merge_request, :without_diffs) }
+
it 'returns the sha of the source branch last commit' do
expect(subject.source_branch_sha).to eq(last_branch_commit.sha)
end
@@ -570,6 +572,7 @@ describe MergeRequest do
context 'when the merge request is being created' do
subject { build(:merge_request, source_branch: nil, compare_commits: []) }
+
it 'returns nil' do
expect(subject.source_branch_sha).to be_nil
end
@@ -2072,7 +2075,7 @@ describe MergeRequest do
end
it 'refuses to enqueue a job if the MR is not open' do
- merge_request.update_column(:state, 'foo')
+ merge_request.update_column(:state_id, 5)
expect(RebaseWorker).not_to receive(:perform_async)
@@ -2495,6 +2498,7 @@ describe MergeRequest do
describe "#diff_refs" do
context "with diffs" do
subject { create(:merge_request, :with_diffs) }
+
let(:expected_diff_refs) do
Gitlab::Diff::DiffRefs.new(
base_sha: subject.merge_request_diff.base_commit_sha,
@@ -2567,32 +2571,32 @@ describe MergeRequest do
describe '#merge_ongoing?' do
it 'returns true when the merge request is locked' do
- merge_request = build_stubbed(:merge_request, state: :locked)
+ merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:locked])
expect(merge_request.merge_ongoing?).to be(true)
end
it 'returns true when merge_id, MR is not merged and it has no running job' do
- merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo')
+ merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:opened], merge_jid: 'foo')
allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { true }
expect(merge_request.merge_ongoing?).to be(true)
end
it 'returns false when merge_jid is nil' do
- merge_request = build_stubbed(:merge_request, state: :open, merge_jid: nil)
+ merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:opened], merge_jid: nil)
expect(merge_request.merge_ongoing?).to be(false)
end
it 'returns false if MR is merged' do
- merge_request = build_stubbed(:merge_request, state: :merged, merge_jid: 'foo')
+ merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:merged], merge_jid: 'foo')
expect(merge_request.merge_ongoing?).to be(false)
end
it 'returns false if there is no merge job running' do
- merge_request = build_stubbed(:merge_request, state: :open, merge_jid: 'foo')
+ merge_request = build_stubbed(:merge_request, state_id: described_class.available_states[:opened], merge_jid: 'foo')
allow(Gitlab::SidekiqStatus).to receive(:running?).with('foo') { false }
expect(merge_request.merge_ongoing?).to be(false)
@@ -2726,7 +2730,7 @@ describe MergeRequest do
context 'closed MR' do
before do
- merge_request.update_attribute(:state, :closed)
+ merge_request.update_attribute(:state_id, described_class.available_states[:closed])
end
it 'is not mergeable' do
@@ -2840,6 +2844,7 @@ describe MergeRequest do
describe '#merge_request_diff_for' do
subject { create(:merge_request, importing: true) }
+
let!(:merge_request_diff1) { subject.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
let!(:merge_request_diff2) { subject.merge_request_diffs.create(head_commit_sha: nil) }
let!(:merge_request_diff3) { subject.merge_request_diffs.create(head_commit_sha: '5937ac0a7beb003549fc5fd26fc247adbce4a52e') }
@@ -2870,6 +2875,7 @@ describe MergeRequest do
describe '#version_params_for' do
subject { create(:merge_request, importing: true) }
+
let(:project) { subject.project }
let!(:merge_request_diff1) { subject.merge_request_diffs.create(head_commit_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') }
let!(:merge_request_diff2) { subject.merge_request_diffs.create(head_commit_sha: nil) }
@@ -3331,4 +3337,6 @@ describe MergeRequest do
it { expect(query).to contain_exactly(merge_request1, merge_request2) }
end
+
+ it_behaves_like 'versioned description'
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 8a47b8c206b..4c320b4b145 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -55,11 +55,13 @@ describe Note do
context 'when noteable and note project are the same' do
subject { create(:note) }
+
it { is_expected.to be_valid }
end
context 'when project is missing for a project related note' do
subject { build(:note, project: nil, noteable: build_stubbed(:issue)) }
+
it { is_expected.to be_invalid }
end
@@ -741,6 +743,7 @@ describe Note do
describe '#to_discussion' do
subject { create(:discussion_note_on_merge_request) }
+
let!(:note2) { create(:discussion_note_on_merge_request, project: subject.project, noteable: subject.noteable, in_reply_to: subject) }
it "returns a discussion with just this note" do
@@ -808,6 +811,7 @@ describe Note do
context 'for a note' do
context 'when part of a discussion' do
subject { create(:discussion_note_on_issue) }
+
let(:note) { create(:discussion_note_on_issue, in_reply_to: subject) }
it 'checks if the note is in reply to the other discussion' do
@@ -821,6 +825,7 @@ describe Note do
context 'when not part of a discussion' do
subject { create(:note) }
+
let(:note) { create(:note, in_reply_to: subject) }
it 'checks if the note is in reply to the other noteable' do
@@ -835,6 +840,7 @@ describe Note do
context 'for a discussion' do
context 'when part of the same discussion' do
subject { create(:diff_note_on_merge_request) }
+
let(:note) { create(:diff_note_on_merge_request, in_reply_to: subject) }
it 'returns true' do
@@ -844,6 +850,7 @@ describe Note do
context 'when not part of the same discussion' do
subject { create(:diff_note_on_merge_request) }
+
let(:note) { create(:diff_note_on_merge_request) }
it 'returns false' do
@@ -855,6 +862,7 @@ describe Note do
context 'for a noteable' do
context 'when a comment on the same noteable' do
subject { create(:note) }
+
let(:note) { create(:note, in_reply_to: subject) }
it 'returns true' do
@@ -864,6 +872,7 @@ describe Note do
context 'when not a comment on the same noteable' do
subject { create(:note) }
+
let(:note) { create(:note) }
it 'returns false' do
@@ -887,6 +896,7 @@ describe Note do
context 'when not part of a discussion' do
subject { create(:note) }
+
let(:note) { create(:note, in_reply_to: subject) }
it 'returns the noteable' do
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index 2e7b2b88432..4b65bf032d1 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -293,11 +293,13 @@ describe PagesDomain do
describe "#https?" do
context "when a certificate is present" do
subject { build(:pages_domain) }
+
it { is_expected.to be_https }
end
context "when no certificate is present" do
subject { build(:pages_domain, :without_certificate) }
+
it { is_expected.not_to be_https }
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index a5048d78363..9f3313e67b5 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -5014,6 +5014,7 @@ describe Project do
describe '#git_objects_poolable?' do
subject { project }
+
context 'when not using hashed storage' do
let(:project) { create(:project, :legacy_storage, :public, :repository) }
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index b089544c810..31d1d1fd7d1 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -109,6 +109,7 @@ describe ProjectWiki do
context "when the wiki repository is empty" do
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_truthy }
end
end
@@ -121,6 +122,7 @@ describe ProjectWiki do
describe '#empty?' do
subject { super().empty? }
+
it { is_expected.to be_falsey }
it 'only instantiates a Wiki page once' do
diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb
index f4023dcb95a..f51041c9ddc 100644
--- a/spec/models/resource_label_event_spec.rb
+++ b/spec/models/resource_label_event_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe ResourceLabelEvent, type: :model do
subject { build(:resource_label_event, issue: issue) }
+
let(:issue) { create(:issue) }
let(:merge_request) { create(:merge_request) }
diff --git a/spec/models/system_note_metadata_spec.rb b/spec/models/system_note_metadata_spec.rb
index bcd3c03f947..801f139355b 100644
--- a/spec/models/system_note_metadata_spec.rb
+++ b/spec/models/system_note_metadata_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
describe SystemNoteMetadata do
describe 'associations' do
it { is_expected.to belong_to(:note) }
+ it { is_expected.to belong_to(:description_version) }
end
describe 'validation' do
diff --git a/spec/models/timelog_spec.rb b/spec/models/timelog_spec.rb
index 28fc82f2a32..7321a458817 100644
--- a/spec/models/timelog_spec.rb
+++ b/spec/models/timelog_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe Timelog do
subject { build(:timelog) }
+
let(:issue) { create(:issue) }
let(:merge_request) { create(:merge_request) }
diff --git a/spec/models/user_interacted_project_spec.rb b/spec/models/user_interacted_project_spec.rb
index 47d919c1d12..b96ff08e22d 100644
--- a/spec/models/user_interacted_project_spec.rb
+++ b/spec/models/user_interacted_project_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
describe UserInteractedProject do
describe '.track' do
subject { described_class.track(event) }
+
let(:event) { build(:event) }
Event::ACTIONS.each do |action|
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 24e66fe14c3..240c917e7cf 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2170,6 +2170,7 @@ describe User do
describe "#contributed_projects" do
subject { create(:user) }
+
let!(:project1) { create(:project) }
let!(:project2) { fork_project(project3) }
let!(:project3) { create(:project) }
diff --git a/spec/policies/identity_provider_policy_spec.rb b/spec/policies/identity_provider_policy_spec.rb
index 2520469d4e7..52b6d2c89ba 100644
--- a/spec/policies/identity_provider_policy_spec.rb
+++ b/spec/policies/identity_provider_policy_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe IdentityProviderPolicy do
subject(:policy) { described_class.new(user, provider) }
+
let(:user) { User.new }
let(:provider) { :a_provider }
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
index 87205f56589..af4c9703eb4 100644
--- a/spec/policies/merge_request_policy_spec.rb
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -53,21 +53,25 @@ describe MergeRequestPolicy do
describe 'the author' do
subject { author }
+
it_behaves_like 'a denied user'
end
describe 'a guest' do
subject { guest }
+
it_behaves_like 'a denied user'
end
describe 'a developer' do
subject { developer }
+
it_behaves_like 'a denied user'
end
describe 'any other user' do
subject { non_team_member }
+
it_behaves_like 'a denied user'
end
end
@@ -82,11 +86,13 @@ describe MergeRequestPolicy do
describe 'a non-team-member' do
subject { non_team_member }
+
it_behaves_like 'a denied user'
end
describe 'a developer' do
subject { developer }
+
it_behaves_like 'a user with access'
end
end
diff --git a/spec/presenters/conversational_development_index/metric_presenter_spec.rb b/spec/presenters/conversational_development_index/metric_presenter_spec.rb
index 81eb05a9a6b..b8b68a676e6 100644
--- a/spec/presenters/conversational_development_index/metric_presenter_spec.rb
+++ b/spec/presenters/conversational_development_index/metric_presenter_spec.rb
@@ -2,6 +2,7 @@ require 'spec_helper'
describe ConversationalDevelopmentIndex::MetricPresenter do
subject { described_class.new(metric) }
+
let(:metric) { build(:conversational_development_index_metric) }
describe '#cards' do
diff --git a/spec/requests/api/commit_statuses_spec.rb b/spec/requests/api/commit_statuses_spec.rb
index 1be8883bd3c..6cb02ba2f6b 100644
--- a/spec/requests/api/commit_statuses_spec.rb
+++ b/spec/requests/api/commit_statuses_spec.rb
@@ -125,25 +125,55 @@ describe API::CommitStatuses do
let(:post_url) { "/projects/#{project.id}/statuses/#{sha}" }
context 'developer user' do
- %w[pending running success failed canceled].each do |status|
- context "for #{status}" do
- context 'uses only required parameters' do
- it 'creates commit status' do
- post api(post_url, developer), params: { state: status }
+ context 'uses only required parameters' do
+ %w[pending running success failed canceled].each do |status|
+ context "for #{status}" do
+ context 'when pipeline for sha does not exists' do
+ it 'creates commit status' do
+ post api(post_url, developer), params: { state: status }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['sha']).to eq(commit.id)
+ expect(json_response['status']).to eq(status)
+ expect(json_response['name']).to eq('default')
+ expect(json_response['ref']).not_to be_empty
+ expect(json_response['target_url']).to be_nil
+ expect(json_response['description']).to be_nil
+
+ if status == 'failed'
+ expect(CommitStatus.find(json_response['id'])).to be_api_failure
+ end
+ end
+ end
+ end
+ end
+
+ context 'when pipeline already exists for the specified sha' do
+ let!(:pipeline) { create(:ci_pipeline, project: project, sha: sha, ref: 'ref') }
+ let(:params) { { state: 'pending' } }
+
+ shared_examples_for 'creates a commit status for the existing pipeline' do
+ it do
+ expect do
+ post api(post_url, developer), params: params
+ end.not_to change { Ci::Pipeline.count }
+
+ job = pipeline.statuses.find_by_name(json_response['name'])
expect(response).to have_gitlab_http_status(201)
- expect(json_response['sha']).to eq(commit.id)
- expect(json_response['status']).to eq(status)
- expect(json_response['name']).to eq('default')
- expect(json_response['ref']).not_to be_empty
- expect(json_response['target_url']).to be_nil
- expect(json_response['description']).to be_nil
-
- if status == 'failed'
- expect(CommitStatus.find(json_response['id'])).to be_api_failure
- end
+ expect(job.status).to eq('pending')
end
end
+
+ it_behaves_like 'creates a commit status for the existing pipeline'
+
+ context 'with pipeline for merge request' do
+ let!(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline, source_project: project) }
+ let!(:pipeline) { merge_request.all_pipelines.last }
+ let(:sha) { pipeline.sha }
+
+ it_behaves_like 'creates a commit status for the existing pipeline'
+ end
end
end
diff --git a/spec/requests/api/graphql/namespace/projects_spec.rb b/spec/requests/api/graphql/namespace/projects_spec.rb
index 815e9531ecf..2a95b99572f 100644
--- a/spec/requests/api/graphql/namespace/projects_spec.rb
+++ b/spec/requests/api/graphql/namespace/projects_spec.rb
@@ -67,6 +67,7 @@ describe 'getting projects' do
context 'when the namespace is a user' do
subject { user.namespace }
+
let(:include_subgroups) { false }
it_behaves_like 'a graphql namespace'
diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb
index c41eabe0a48..28abe1a8456 100644
--- a/spec/requests/api/pages/internal_access_spec.rb
+++ b/spec/requests/api/pages/internal_access_spec.rb
@@ -27,6 +27,7 @@ describe "Internal Project Pages Access" do
describe "Project should be internal" do
describe '#internal?' do
subject { project.internal? }
+
it { is_expected.to be_truthy }
end
end
diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb
index c647537038e..6af441caf74 100644
--- a/spec/requests/api/pages/private_access_spec.rb
+++ b/spec/requests/api/pages/private_access_spec.rb
@@ -27,6 +27,7 @@ describe "Private Project Pages Access" do
describe "Project should be private" do
describe '#private?' do
subject { project.private? }
+
it { is_expected.to be_truthy }
end
end
diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb
index 16cc5697f30..d99224eca5b 100644
--- a/spec/requests/api/pages/public_access_spec.rb
+++ b/spec/requests/api/pages/public_access_spec.rb
@@ -27,6 +27,7 @@ describe "Public Project Pages Access" do
describe "Project should be public" do
describe '#public?' do
subject { project.public? }
+
it { is_expected.to be_truthy }
end
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index bc3a04420f9..70a95663aea 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe API::Runner, :clean_gitlab_redis_shared_state do
include StubGitlabCalls
include RedisHelpers
+ include WorkhorseHelpers
let(:registration_token) { 'abcdefg123456' }
@@ -1395,7 +1396,7 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
expect(response).to have_gitlab_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
- expect(json_response['TempPath']).to eq(JobArtifactUploader.workhorse_local_upload_path)
+ expect(json_response).not_to have_key('TempPath')
expect(json_response['RemoteObject']).to have_key('ID')
expect(json_response['RemoteObject']).to have_key('GetURL')
expect(json_response['RemoteObject']).to have_key('StoreURL')
@@ -1562,15 +1563,16 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
let!(:fog_connection) do
stub_artifacts_object_storage(direct_upload: true)
end
-
- before do
+ let(:object) do
fog_connection.directories.new(key: 'artifacts').files.create(
key: 'tmp/uploads/12312300',
body: 'content'
)
+ end
+ let(:file_upload) { fog_to_uploaded_file(object) }
- upload_artifacts(file_upload, headers_with_token,
- { 'file.remote_id' => remote_id })
+ before do
+ upload_artifacts(file_upload, headers_with_token, 'file.remote_id' => remote_id)
end
context 'when valid remote_id is used' do
@@ -1804,12 +1806,13 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
end
def upload_artifacts(file, headers = {}, params = {})
- params = params.merge({
- 'file.path' => file.path,
- 'file.name' => file.original_filename
- })
-
- post api("/jobs/#{job.id}/artifacts"), params: params, headers: headers
+ workhorse_finalize(
+ api("/jobs/#{job.id}/artifacts"),
+ method: :post,
+ file_key: :file,
+ params: params.merge(file: file),
+ headers: headers
+ )
end
end
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb
index ae34f7d1f87..62b9ee1d361 100644
--- a/spec/requests/lfs_http_spec.rb
+++ b/spec/requests/lfs_http_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe 'Git LFS API and storage' do
include LfsHttpHelpers
include ProjectForksHelper
+ include WorkhorseHelpers
set(:project) { create(:project, :repository) }
set(:other_project) { create(:project, :repository) }
@@ -933,7 +934,7 @@ describe 'Git LFS API and storage' do
it_behaves_like 'a valid response' do
it 'responds with status 200, location of LFS remote store and object details' do
- expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
+ expect(json_response).not_to have_key('TempPath')
expect(json_response['RemoteObject']).to have_key('ID')
expect(json_response['RemoteObject']).to have_key('GetURL')
expect(json_response['RemoteObject']).to have_key('StoreURL')
@@ -992,10 +993,17 @@ describe 'Git LFS API and storage' do
stub_lfs_object_storage(direct_upload: true)
end
+ let(:tmp_object) do
+ fog_connection.directories.new(key: 'lfs-objects').files.create(
+ key: 'tmp/uploads/12312300',
+ body: 'content'
+ )
+ end
+
['123123', '../../123123'].each do |remote_id|
context "with invalid remote_id: #{remote_id}" do
subject do
- put_finalize(with_tempfile: true, args: {
+ put_finalize(remote_object: tmp_object, args: {
'file.remote_id' => remote_id
})
end
@@ -1009,15 +1017,8 @@ describe 'Git LFS API and storage' do
end
context 'with valid remote_id' do
- before do
- fog_connection.directories.new(key: 'lfs-objects').files.create(
- key: 'tmp/uploads/12312300',
- body: 'content'
- )
- end
-
subject do
- put_finalize(with_tempfile: true, args: {
+ put_finalize(remote_object: tmp_object, args: {
'file.remote_id' => '12312300',
'file.name' => 'name'
})
@@ -1027,6 +1028,10 @@ describe 'Git LFS API and storage' do
subject
expect(response).to have_gitlab_http_status(200)
+
+ object = LfsObject.find_by_oid(sample_oid)
+ expect(object).to be_present
+ expect(object.file.read).to eq(tmp_object.body)
end
it 'schedules migration of file to object storage' do
@@ -1268,28 +1273,31 @@ describe 'Git LFS API and storage' do
put authorize_url(project, sample_oid, sample_size), params: {}, headers: authorize_headers
end
- def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, verified: true, args: {})
- upload_path = LfsObjectUploader.workhorse_local_upload_path
- file_path = upload_path + '/' + lfs_tmp if lfs_tmp
+ def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, verified: true, remote_object: nil, args: {})
+ uploaded_file = nil
if with_tempfile
+ upload_path = LfsObjectUploader.workhorse_local_upload_path
+ file_path = upload_path + '/' + lfs_tmp if lfs_tmp
+
FileUtils.mkdir_p(upload_path)
FileUtils.touch(file_path)
- end
-
- extra_args = {
- 'file.path' => file_path,
- 'file.name' => File.basename(file_path)
- }
- put_finalize_with_args(args.merge(extra_args).compact, verified: verified)
- end
+ uploaded_file = UploadedFile.new(file_path, filename: File.basename(file_path))
+ elsif remote_object
+ uploaded_file = fog_to_uploaded_file(remote_object)
+ end
- def put_finalize_with_args(args, verified:)
finalize_headers = headers
finalize_headers.merge!(workhorse_internal_api_request_header) if verified
- put objects_url(project, sample_oid, sample_size), params: args, headers: finalize_headers
+ workhorse_finalize(
+ objects_url(project, sample_oid, sample_size),
+ method: :put,
+ file_key: :file,
+ params: args.merge(file: uploaded_file),
+ headers: finalize_headers
+ )
end
def lfs_tmp_file
diff --git a/spec/rubocop/cop/migration/add_timestamps_spec.rb b/spec/rubocop/cop/migration/add_timestamps_spec.rb
index fae0177d5f5..33f1bb85af8 100644
--- a/spec/rubocop/cop/migration/add_timestamps_spec.rb
+++ b/spec/rubocop/cop/migration/add_timestamps_spec.rb
@@ -9,6 +9,7 @@ describe RuboCop::Cop::Migration::AddTimestamps do
include CopHelper
subject(:cop) { described_class.new }
+
let(:migration_with_add_timestamps) do
%q(
class Users < ActiveRecord::Migration[4.2]
diff --git a/spec/rubocop/cop/migration/timestamps_spec.rb b/spec/rubocop/cop/migration/timestamps_spec.rb
index 1812818692a..cafe255dc9a 100644
--- a/spec/rubocop/cop/migration/timestamps_spec.rb
+++ b/spec/rubocop/cop/migration/timestamps_spec.rb
@@ -9,6 +9,7 @@ describe RuboCop::Cop::Migration::Timestamps do
include CopHelper
subject(:cop) { described_class.new }
+
let(:migration_with_timestamps) do
%q(
class Users < ActiveRecord::Migration[4.2]
diff --git a/spec/rubocop/cop/scalability/file_uploads_spec.rb b/spec/rubocop/cop/scalability/file_uploads_spec.rb
index 2a94fde5ba2..a35d423581c 100644
--- a/spec/rubocop/cop/scalability/file_uploads_spec.rb
+++ b/spec/rubocop/cop/scalability/file_uploads_spec.rb
@@ -10,6 +10,7 @@ describe RuboCop::Cop::Scalability::FileUploads do
include ExpectOffense
subject(:cop) { described_class.new }
+
let(:message) { 'Do not upload files without workhorse acceleration. Please refer to https://docs.gitlab.com/ee/development/uploads.html' }
context 'with required params' do
diff --git a/spec/serializers/cluster_basic_entity_spec.rb b/spec/serializers/cluster_basic_entity_spec.rb
index ab5d54fca16..be03ee91784 100644
--- a/spec/serializers/cluster_basic_entity_spec.rb
+++ b/spec/serializers/cluster_basic_entity_spec.rb
@@ -5,6 +5,7 @@ require 'spec_helper'
describe ClusterBasicEntity do
describe '#as_json' do
subject { described_class.new(cluster, request: request).as_json }
+
let(:maintainer) { create(:user) }
let(:developer) { create(:user) }
let(:current_user) { maintainer }
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index 9762c83ed6a..ce5264ec8bb 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -139,6 +139,7 @@ describe PipelineSerializer do
describe 'number of queries when preloaded' do
subject { serializer.represent(resource, preload: true) }
+
let(:resource) { Ci::Pipeline.all }
before do
diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb
index c97d4d38b1c..abb5b9b130b 100644
--- a/spec/services/git/tag_hooks_service_spec.rb
+++ b/spec/services/git/tag_hooks_service_spec.rb
@@ -58,6 +58,7 @@ describe Git::TagHooksService, :service do
describe 'Push data' do
shared_examples_for 'tag push data expectations' do
subject(:push_data) { service.send(:push_data) }
+
it 'has expected push data attributes' do
is_expected.to match a_hash_including(
object_kind: 'tag_push',
diff --git a/spec/services/merge_requests/create_from_issue_service_spec.rb b/spec/services/merge_requests/create_from_issue_service_spec.rb
index 07e0218e1df..51a5c51f6c3 100644
--- a/spec/services/merge_requests/create_from_issue_service_spec.rb
+++ b/spec/services/merge_requests/create_from_issue_service_spec.rb
@@ -13,6 +13,7 @@ describe MergeRequests::CreateFromIssueService do
let(:custom_source_branch) { 'custom-source-branch' }
subject(:service) { described_class.new(project, user, service_params) }
+
subject(:service_with_custom_source_branch) { described_class.new(project, user, branch_name: custom_source_branch, **service_params) }
before do
diff --git a/spec/services/note_summary_spec.rb b/spec/services/note_summary_spec.rb
index e59731207a5..aa4e41f4d8c 100644
--- a/spec/services/note_summary_spec.rb
+++ b/spec/services/note_summary_spec.rb
@@ -46,5 +46,17 @@ describe NoteSummary do
it 'returns metadata hash' do
expect(create_note_summary.metadata).to eq(action: 'icon', commit_count: 5)
end
+
+ context 'description action and noteable has saved_description_version' do
+ before do
+ noteable.saved_description_version = 1
+ end
+
+ subject { described_class.new(noteable, project, user, 'note', action: 'description') }
+
+ it 'sets the description_version metadata' do
+ expect(subject.metadata).to include(description_version: 1)
+ end
+ end
end
end
diff --git a/spec/services/projects/housekeeping_service_spec.rb b/spec/services/projects/housekeeping_service_spec.rb
index f651db70cbd..c99054d9fd5 100644
--- a/spec/services/projects/housekeeping_service_spec.rb
+++ b/spec/services/projects/housekeeping_service_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe Projects::HousekeepingService do
subject { described_class.new(project) }
+
set(:project) { create(:project, :repository) }
before do
diff --git a/spec/services/system_notes/issuables_service_spec.rb b/spec/services/system_notes/issuables_service_spec.rb
index a914b34cb23..5023abad4cd 100644
--- a/spec/services/system_notes/issuables_service_spec.rb
+++ b/spec/services/system_notes/issuables_service_spec.rb
@@ -212,6 +212,15 @@ describe ::SystemNotes::IssuablesService do
it 'sets the note text' do
expect(subject.note).to eq('changed the description')
end
+
+ it 'associates the related description version' do
+ noteable.update!(description: 'New description')
+
+ description_version_id = subject.system_note_metadata.description_version_id
+
+ expect(description_version_id).not_to be_nil
+ expect(description_version_id).to eq(noteable.saved_description_version.id)
+ end
end
end
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index da743e586f5..e74dbca4f93 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -319,10 +319,10 @@ module KubernetesHelpers
}
end
- def kube_knative_services_body(**options)
+ def kube_knative_services_body(legacy_knative: false, **options)
{
"kind" => "List",
- "items" => [kube_service(options)]
+ "items" => [legacy_knative ? knative_05_service(options) : kube_service(options)]
}
end
@@ -421,6 +421,27 @@ module KubernetesHelpers
}
end
+ def knative_05_service(name: "kubetest", namespace: "default", domain: "example.com")
+ {
+ "metadata" => {
+ "creationTimestamp" => "2018-11-21T06:16:33Z",
+ "name" => name,
+ "namespace" => namespace,
+ "selfLink" => "/apis/serving.knative.dev/v1alpha1/namespaces/#{namespace}/services/#{name}"
+ },
+ "spec" => {
+ "generation" => 2
+ },
+ "status" => {
+ "domain" => "#{name}.#{namespace}.#{domain}",
+ "domainInternal" => "#{name}.#{namespace}.svc.cluster.local",
+ "latestCreatedRevisionName" => "#{name}-00002",
+ "latestReadyRevisionName" => "#{name}-00002",
+ "observedGeneration" => 2
+ }
+ }
+ end
+
def kube_service_full(name: "kubetest", namespace: "kube-ns", domain: "example.com")
{
"metadata" => {
diff --git a/spec/support/helpers/workhorse_helpers.rb b/spec/support/helpers/workhorse_helpers.rb
index 40007a14b85..e0fba191deb 100644
--- a/spec/support/helpers/workhorse_helpers.rb
+++ b/spec/support/helpers/workhorse_helpers.rb
@@ -22,16 +22,40 @@ module WorkhorseHelpers
# workhorse_post_with_file will transform file_key inside params as if it was disk accelerated by workhorse
def workhorse_post_with_file(url, file_key:, params:)
+ workhorse_request_with_file(:post, url,
+ file_key: file_key,
+ params: params,
+ env: { 'CONTENT_TYPE' => 'multipart/form-data' },
+ send_rewritten_field: true
+ )
+ end
+
+ # workhorse_finalize will transform file_key inside params as if it was the finalize call of an inline object storage upload.
+ # note that based on the content of the params it can simulate a disc acceleration or an object storage upload
+ def workhorse_finalize(url, method: :post, file_key:, params:, headers: {})
+ workhorse_request_with_file(method, url,
+ file_key: file_key,
+ params: params,
+ extra_headers: headers,
+ send_rewritten_field: false
+ )
+ end
+
+ def workhorse_request_with_file(method, url, file_key:, params:, env: {}, extra_headers: {}, send_rewritten_field:)
workhorse_params = params.dup
file = workhorse_params.delete(file_key)
- workhorse_params.merge!(workhorse_disk_accelerated_file_params(file_key, file))
+ workhorse_params = workhorse_disk_accelerated_file_params(file_key, file).merge(workhorse_params)
+
+ headers = if send_rewritten_field
+ workhorse_rewritten_fields_header(file_key => file.path)
+ else
+ {}
+ end
+
+ headers.merge!(extra_headers)
- post(url,
- params: workhorse_params,
- headers: workhorse_rewritten_fields_header(file_key => file.path),
- env: { 'CONTENT_TYPE' => 'multipart/form-data' }
- )
+ process(method, url, params: workhorse_params, headers: headers, env: env)
end
private
@@ -45,9 +69,24 @@ module WorkhorseHelpers
end
def workhorse_disk_accelerated_file_params(key, file)
+ return {} unless file
+
{
"#{key}.name" => file.original_filename,
- "#{key}.path" => file.path
- }
+ "#{key}.size" => file.size
+ }.tap do |params|
+ params["#{key}.path"] = file.path if file.path
+ params["#{key}.remote_id"] = file.remote_id if file.respond_to?(:remote_id) && file.remote_id
+ end
+ end
+
+ def fog_to_uploaded_file(file)
+ filename = File.basename(file.key)
+
+ UploadedFile.new(nil,
+ filename: filename,
+ remote_id: filename,
+ size: file.content_length
+ )
end
end
diff --git a/spec/support/redis/redis_shared_examples.rb b/spec/support/redis/redis_shared_examples.rb
index 7e47cdae866..97a23f02b3e 100644
--- a/spec/support/redis/redis_shared_examples.rb
+++ b/spec/support/redis/redis_shared_examples.rb
@@ -90,6 +90,7 @@ RSpec.shared_examples "redis_shared_examples" do
describe '._raw_config' do
subject { described_class._raw_config }
+
let(:config_file_name) { '/var/empty/doesnotexist' }
it 'is frozen' do
diff --git a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
index 0b4ab9941fc..c24418b2f90 100644
--- a/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
+++ b/spec/support/shared_examples/controllers/uploads_actions_shared_examples.rb
@@ -338,7 +338,7 @@ shared_examples 'handle uploads authorize' do
it_behaves_like 'a valid response' do
it 'responds with status 200, location of uploads remote store and object details' do
- expect(json_response['TempPath']).to eq(uploader_class.workhorse_local_upload_path)
+ expect(json_response).not_to have_key('TempPath')
expect(json_response['RemoteObject']).to have_key('ID')
expect(json_response['RemoteObject']).to have_key('GetURL')
expect(json_response['RemoteObject']).to have_key('StoreURL')
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index f95b612eb70..a6653f89377 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -111,6 +111,7 @@ shared_examples 'cluster application status specs' do |application_name|
describe '#make_errored' do
subject { create(application_name, :installing) }
+
let(:reason) { 'some errors' }
it 'is errored' do
diff --git a/spec/support/shared_examples/versioned_description_shared_examples.rb b/spec/support/shared_examples/versioned_description_shared_examples.rb
new file mode 100644
index 00000000000..59124af19ec
--- /dev/null
+++ b/spec/support/shared_examples/versioned_description_shared_examples.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'versioned description' do
+ describe 'associations' do
+ it { is_expected.to have_many(:description_versions) }
+ end
+
+ describe 'save_description_version' do
+ let(:factory_name) { described_class.name.underscore.to_sym }
+ let!(:model) { create(factory_name, description: 'Original description') }
+
+ context 'when feature is enabled' do
+ before do
+ stub_feature_flags(save_description_versions: true)
+ end
+
+ context 'when description was changed' do
+ before do
+ model.update!(description: 'New description')
+ end
+
+ it 'saves the old and new description for the first update' do
+ expect(model.description_versions.first.description).to eq('Original description')
+ expect(model.description_versions.last.description).to eq('New description')
+ end
+
+ it 'only saves the new description for subsequent updates' do
+ expect { model.update!(description: 'Another description') }.to change { model.description_versions.count }.by(1)
+
+ expect(model.description_versions.last.description).to eq('Another description')
+ end
+
+ it 'sets the new description version to `saved_description_version`' do
+ expect(model.saved_description_version).to eq(model.description_versions.last)
+ end
+
+ it 'clears `saved_description_version` after another save that does not change description' do
+ model.save!
+
+ expect(model.saved_description_version).to be_nil
+ end
+ end
+
+ context 'when description was not changed' do
+ it 'does not save any description version' do
+ expect { model.save! }.not_to change { model.description_versions.count }
+
+ expect(model.saved_description_version).to be_nil
+ end
+ end
+ end
+
+ context 'when feature is disabled' do
+ before do
+ stub_feature_flags(save_description_versions: false)
+ end
+
+ it 'does not save any description version' do
+ expect { model.update!(description: 'New description') }.not_to change { model.description_versions.count }
+
+ expect(model.saved_description_version).to be_nil
+ end
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/check_rake_spec.rb b/spec/tasks/gitlab/check_rake_spec.rb
index 1469143d2ac..b3c8ca03aec 100644
--- a/spec/tasks/gitlab/check_rake_spec.rb
+++ b/spec/tasks/gitlab/check_rake_spec.rb
@@ -19,6 +19,7 @@ describe 'check.rake' do
describe 'gitlab:check rake task' do
subject { run_rake_task('gitlab:check') }
+
let(:name) { 'GitLab subtasks' }
it_behaves_like 'system check rake task'
@@ -26,6 +27,7 @@ describe 'check.rake' do
describe 'gitlab:gitlab_shell:check rake task' do
subject { run_rake_task('gitlab:gitlab_shell:check') }
+
let(:name) { 'GitLab Shell' }
it_behaves_like 'system check rake task'
@@ -33,6 +35,7 @@ describe 'check.rake' do
describe 'gitlab:gitaly:check rake task' do
subject { run_rake_task('gitlab:gitaly:check') }
+
let(:name) { 'Gitaly' }
it_behaves_like 'system check rake task'
@@ -40,6 +43,7 @@ describe 'check.rake' do
describe 'gitlab:sidekiq:check rake task' do
subject { run_rake_task('gitlab:sidekiq:check') }
+
let(:name) { 'Sidekiq' }
it_behaves_like 'system check rake task'
@@ -47,6 +51,7 @@ describe 'check.rake' do
describe 'gitlab:incoming_email:check rake task' do
subject { run_rake_task('gitlab:incoming_email:check') }
+
let(:name) { 'Incoming Email' }
it_behaves_like 'system check rake task'
@@ -56,6 +61,7 @@ describe 'check.rake' do
include LdapHelpers
subject { run_rake_task('gitlab:ldap:check') }
+
let(:name) { 'LDAP' }
it_behaves_like 'system check rake task'
diff --git a/spec/workers/every_sidekiq_worker_spec.rb b/spec/workers/every_sidekiq_worker_spec.rb
index 8fddd8540ef..b7ba4d61723 100644
--- a/spec/workers/every_sidekiq_worker_spec.rb
+++ b/spec/workers/every_sidekiq_worker_spec.rb
@@ -35,4 +35,32 @@ describe 'Every Sidekiq worker' do
expect(config_queues).to include(queue).or(include(queue_namespace))
end
end
+
+ describe "feature category declarations" do
+ let(:feature_categories) do
+ YAML.load_file(Rails.root.join('config', 'feature_categories.yml')).map(&:to_sym).to_set
+ end
+
+ # All Sidekiq worker classes should declare a valid `feature_category`
+ # or explicitely be excluded with the `feature_category_not_owned!` annotation.
+ # Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details.
+ it 'has a feature_category or feature_category_not_owned! attribute', :aggregate_failures do
+ Gitlab::SidekiqConfig.workers.each do |worker|
+ expect(worker.get_feature_category).to be_a(Symbol), "expected #{worker.inspect} to declare a feature_category or feature_category_not_owned!"
+ end
+ end
+
+ # All Sidekiq worker classes should declare a valid `feature_category`.
+ # The category should match a value in `config/feature_categories.yml`.
+ # Please see doc/development/sidekiq_style_guide.md#Feature-Categorization for more details.
+ it 'has a feature_category that maps to a value in feature_categories.yml', :aggregate_failures do
+ workers_with_feature_categories = Gitlab::SidekiqConfig.workers
+ .select(&:get_feature_category)
+ .reject(&:feature_category_not_owned?)
+
+ workers_with_feature_categories.each do |worker|
+ expect(feature_categories).to include(worker.get_feature_category), "expected #{worker.inspect} to declare a valid feature_category, but got #{worker.get_feature_category}"
+ end
+ end
+ end
end
diff --git a/spec/workers/hashed_storage/migrator_worker_spec.rb b/spec/workers/hashed_storage/migrator_worker_spec.rb
index a318cdd003e..12c1a26104e 100644
--- a/spec/workers/hashed_storage/migrator_worker_spec.rb
+++ b/spec/workers/hashed_storage/migrator_worker_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe HashedStorage::MigratorWorker do
subject(:worker) { described_class.new }
+
let(:projects) { create_list(:project, 2, :legacy_storage, :empty_repo) }
let(:ids) { projects.map(&:id) }
diff --git a/spec/workers/hashed_storage/rollbacker_worker_spec.rb b/spec/workers/hashed_storage/rollbacker_worker_spec.rb
index 4055f380978..5fcb1adf9ae 100644
--- a/spec/workers/hashed_storage/rollbacker_worker_spec.rb
+++ b/spec/workers/hashed_storage/rollbacker_worker_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
describe HashedStorage::RollbackerWorker do
subject(:worker) { described_class.new }
+
let(:projects) { create_list(:project, 2, :empty_repo) }
let(:ids) { projects.map(&:id) }
diff --git a/vendor/gitignore/C++.gitignore b/vendor/gitignore/C++.gitignore
index 259148fa18f..259148fa18f 100755..100644
--- a/vendor/gitignore/C++.gitignore
+++ b/vendor/gitignore/C++.gitignore
diff --git a/vendor/gitignore/Java.gitignore b/vendor/gitignore/Java.gitignore
index a1c2a238a96..a1c2a238a96 100755..100644
--- a/vendor/gitignore/Java.gitignore
+++ b/vendor/gitignore/Java.gitignore