Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-07 15:11:00 +00:00
parent 1dab074ef1
commit fa4473a487
52 changed files with 1063 additions and 521 deletions

View File

@ -784,6 +784,10 @@ Style/RegexpLiteralMixedPreserve:
- mixed_preserve
EnforcedStyle: mixed_preserve
# See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94317#note_1139610896
Style/Lambda:
EnforcedStyle: literal
RSpec/TopLevelDescribePath:
Exclude:
- 'spec/fixtures/**/*.rb'

View File

@ -1,273 +1,81 @@
---
# Cop supports --auto-correct.
# Cop supports --autocorrect.
Style/Lambda:
# Offense count: 653
# Temporarily disabled due to too many offenses
Enabled: false
Details: grace period
Exclude:
- 'app/controllers/concerns/notes_actions.rb'
- 'app/controllers/concerns/spammable_actions/captcha_check/rest_api_actions_support.rb'
- 'app/controllers/projects/issues_controller.rb'
- 'app/controllers/search_controller.rb'
- 'app/graphql/mutations/container_repositories/destroy_tags.rb'
- 'app/graphql/mutations/design_management/delete.rb'
- 'app/graphql/types/permission_types/base_permission_type.rb'
- 'app/models/analytics/cycle_analytics/issue_stage_event.rb'
- 'app/models/analytics/cycle_analytics/merge_request_stage_event.rb'
- 'app/models/bulk_imports/tracker.rb'
- 'app/models/ci/build.rb'
- 'app/models/ci/deleted_object.rb'
- 'app/models/ci/instance_variable.rb'
- 'app/models/ci/job_artifact.rb'
- 'app/models/ci/namespace_mirror.rb'
- 'app/models/ci/pending_build.rb'
- 'app/models/ci/pipeline.rb'
- 'app/models/ci/processable.rb'
- 'app/models/ci/runner.rb'
- 'app/models/clusters/cluster.rb'
- 'app/models/clusters/concerns/application_status.rb'
- 'app/models/commit_status.rb'
- 'app/models/concerns/analytics/cycle_analytics/stage_event_model.rb'
- 'app/models/concerns/approvable_base.rb'
- 'app/models/concerns/atomic_internal_id.rb'
- 'app/models/concerns/ci/has_status.rb'
- 'app/models/concerns/clusters/agents/authorization_config_scopes.rb'
- 'app/models/concerns/has_environment_scope.rb'
- 'app/models/concerns/has_wiki_page_meta_attributes.rb'
- 'app/models/concerns/id_in_ordered.rb'
- 'app/models/concerns/integrations/has_issue_tracker_fields.rb'
- 'app/models/concerns/issuable.rb'
- 'app/models/concerns/issue_resource_event.rb'
- 'app/models/concerns/milestoneable.rb'
- 'app/models/concerns/mirror_authentication.rb'
- 'app/models/concerns/packages/debian/component_file.rb'
- 'app/models/concerns/reactive_caching.rb'
- 'app/models/concerns/timebox.rb'
- 'app/models/container_repository.rb'
- 'app/models/custom_emoji.rb'
- 'app/models/deployment.rb'
- 'app/models/design_management/action.rb'
- 'app/models/design_management/design.rb'
- 'app/models/design_management/version.rb'
- 'app/models/environment.rb'
- 'app/models/event.rb'
- 'app/models/group.rb'
- 'app/models/group_deploy_key.rb'
- 'app/models/group_group_link.rb'
- 'app/models/hooks/web_hook.rb'
- 'app/models/identity.rb'
- 'app/models/import_failure.rb'
- 'app/models/integrations/zentao_tracker_data.rb'
- 'app/models/internal_id.rb'
- 'app/models/issue.rb'
- 'app/models/issue/metrics.rb'
- 'app/models/jira_connect_installation.rb'
- 'app/models/label.rb'
- 'app/models/label_link.rb'
- 'app/models/loose_foreign_keys/deleted_record.rb'
- 'app/models/member.rb'
- 'app/models/members/project_member.rb'
- 'app/models/merge_request.rb'
- 'app/models/merge_request/cleanup_schedule.rb'
- 'app/models/merge_request_diff.rb'
- 'app/models/merge_request_diff_file.rb'
- 'app/models/merge_requests_closing_issues.rb'
- 'app/models/milestone.rb'
- 'app/models/namespace.rb'
- 'app/models/note.rb'
- 'app/models/note_diff_file.rb'
- 'app/models/notification_setting.rb'
- 'app/models/onboarding/progress.rb'
- 'app/models/operations/feature_flags/user_list.rb'
- 'app/models/packages/package.rb'
- 'app/models/packages/package_file.rb'
- 'app/models/pages_domain.rb'
- 'app/models/product_analytics_event.rb'
- 'app/models/programming_language.rb'
- 'app/models/project.rb'
- 'app/controllers/concerns/project_unauthorized.rb'
- 'app/controllers/profiles/two_factor_auths_controller.rb'
- 'app/models/concerns/featurable.rb'
- 'app/models/project_feature.rb'
- 'app/models/project_feature_usage.rb'
- 'app/models/projects/topic.rb'
- 'app/models/prometheus_alert_event.rb'
- 'app/models/raw_usage_data.rb'
- 'app/models/redirect_route.rb'
- 'app/models/release.rb'
- 'app/models/remote_mirror.rb'
- 'app/models/repository_language.rb'
- 'app/models/snippet.rb'
- 'app/models/timelog.rb'
- 'app/models/todo.rb'
- 'app/models/user.rb'
- 'app/models/users/in_product_marketing_email.rb'
- 'app/serializers/ci/daily_build_group_report_result_entity.rb'
- 'app/serializers/group_child_entity.rb'
- 'app/serializers/issuable_sidebar_basic_entity.rb'
- 'app/serializers/merge_request_sidebar_basic_entity.rb'
- 'app/services/issues/referenced_merge_requests_service.rb'
- 'config/initializers/deprecations.rb'
- 'config/initializers/rspec_profiling.rb'
- 'config/application.rb'
- 'config/initializers/0_license.rb'
- 'config/initializers/0_log_deprecations.rb'
- 'config/initializers/action_cable.rb'
- 'config/initializers/gitlab_experiment.rb'
- 'config/initializers/lograge.rb'
- 'config/routes.rb'
- 'config/routes/dashboard.rb'
- 'config/routes/group.rb'
- 'config/routes/issues.rb'
- 'db/post_migrate/20210303121224_update_gitlab_subscriptions_start_at_post_eoa.rb'
- 'db/post_migrate/20210513155546_backfill_nuget_temporary_packages_to_processing_status.rb'
- 'db/post_migrate/20210823132600_remove_duplicate_dast_site_tokens.rb'
- 'db/post_migrate/20220425121435_backfill_integrations_enable_ssl_verification.rb'
- 'ee/app/controllers/groups/analytics/productivity_analytics_controller.rb'
- 'ee/app/models/analytics/devops_adoption/enabled_namespace.rb'
- 'ee/app/models/analytics/devops_adoption/snapshot.rb'
- 'ee/app/models/app_sec/fuzzing/coverage/corpus.rb'
- 'ee/app/models/approval_merge_request_rule.rb'
- 'ee/app/models/boards/epic_board_position.rb'
- 'ee/app/models/boards/epic_user_preference.rb'
- 'ee/app/models/ci/minutes/project_monthly_usage.rb'
- 'ee/app/models/concerns/ee/protected_ref.rb'
- 'ee/app/models/concerns/geo/replicable_model.rb'
- 'ee/app/models/concerns/issue_widgets/acts_like_requirement.rb'
- 'ee/app/models/dast/profile.rb'
- 'ee/app/models/dast_site_validation.rb'
- 'ee/app/models/dora/daily_metrics.rb'
- 'ee/app/models/ee/ci/build.rb'
- 'ee/app/models/ee/ci/daily_build_group_report_result.rb'
- 'ee/app/models/ee/ci/job_artifact.rb'
- 'ee/app/models/ee/ci/pipeline.rb'
- 'ee/app/models/ee/environment.rb'
- 'ee/app/models/ee/epic.rb'
- 'ee/app/models/ee/group.rb'
- 'ee/app/models/ee/group_member.rb'
- 'ee/app/models/ee/identity.rb'
- 'ee/app/models/ee/issue.rb'
- 'ee/app/models/ee/iteration.rb'
- 'ee/app/models/ee/label.rb'
- 'ee/app/models/ee/member.rb'
- 'ee/app/models/ee/merge_request.rb'
- 'ee/app/models/ee/namespace.rb'
- 'ee/app/models/ee/namespace_ci_cd_setting.rb'
- 'ee/app/models/ee/note.rb'
- 'ee/app/models/ee/project.rb'
- 'ee/app/models/ee/user.rb'
- 'ee/app/models/ee/vulnerability.rb'
- 'ee/app/models/gitlab_subscription.rb'
- 'ee/app/models/incident_management/oncall_rotation.rb'
- 'ee/app/models/incident_management/oncall_shift.rb'
- 'ee/app/models/iterations/cadence.rb'
- 'ee/app/models/merge_request_block.rb'
- 'ee/app/models/merge_requests/external_status_check.rb'
- 'ee/app/models/merge_train.rb'
- 'ee/app/models/protected_environment.rb'
- 'ee/app/models/requirements_management/requirement.rb'
- 'ee/app/models/security/finding.rb'
- 'ee/app/models/security/orchestration_policy_configuration.rb'
- 'ee/app/models/security/orchestration_policy_rule_schedule.rb'
- 'ee/app/models/security/scan.rb'
- 'ee/app/models/security/training_provider.rb'
- 'ee/app/models/software_license_policy.rb'
- 'ee/app/models/vulnerabilities/feedback.rb'
- 'ee/app/models/vulnerabilities/finding.rb'
- 'ee/app/models/vulnerabilities/historical_statistic.rb'
- 'ee/app/models/vulnerabilities/read.rb'
- 'ee/app/models/vulnerabilities/scanner.rb'
- 'ee/app/controllers/concerns/ee/routable_actions/sso_enforcement_redirect.rb'
- 'ee/app/serializers/ee/group_child_entity.rb'
- 'ee/lib/ee/api/entities/application_setting.rb'
- 'ee/lib/ee/api/entities/geo_node_status.rb'
- 'ee/lib/ee/api/entities/group.rb'
- 'ee/app/services/ee/issues/export_csv_service.rb'
- 'ee/lib/ee/api/entities/group_detail.rb'
- 'ee/lib/ee/api/entities/group_push_rule.rb'
- 'ee/lib/ee/api/entities/project.rb'
- 'ee/lib/ee/api/entities/vulnerability_issue_link.rb'
- 'ee/lib/ee/gitlab/background_migration/populate_resolved_on_default_branch_column.rb'
- 'ee/lib/ee/banzai/filter/sanitization_filter.rb'
- 'ee/lib/ee/gitlab/checks/diff_check.rb'
- 'ee/lib/elastic/latest/application_class_proxy.rb'
- 'ee/lib/gem_extensions/elasticsearch/model/adapter/active_record/importing.rb'
- 'ee/spec/migrations/backfill_delayed_group_deletion_spec.rb'
- 'ee/spec/migrations/remove_schedule_and_status_null_constraints_from_pending_escalations_alert_spec.rb'
- 'ee/spec/elastic_integration/global_search_spec.rb'
- 'ee/spec/lib/gitlab/geo/event_gap_tracking_spec.rb'
- 'ee/spec/services/ee/groups/autocomplete_service_spec.rb'
- 'ee/spec/services/ee/notes/create_service_spec.rb'
- 'ee/spec/support/helpers/elasticsearch_helpers.rb'
- 'ee/spec/support/shared_examples/lib/gitlab/middleware/maintenance_mode_gitlab_ee_instance_shared_examples.rb'
- 'ee/spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_ee_instance_shared_examples.rb'
- 'lib/api/ci/jobs.rb'
- 'lib/api/ci/pipelines.rb'
- 'lib/api/entities/group_detail.rb'
- 'lib/api/entities/issue.rb'
- 'lib/api/entities/label.rb'
- 'lib/api/entities/merge_request.rb'
- 'lib/api/entities/project.rb'
- 'lib/api/entities/project_export_status.rb'
- 'lib/api/feature_flags_user_lists.rb'
- 'lib/container_registry/base_client.rb'
- 'lib/container_registry/client.rb'
- 'lib/csv_builder.rb'
- 'lib/event_filter.rb'
- 'lib/gitlab/background_migration/backfill_ci_namespace_mirrors.rb'
- 'lib/gitlab/background_migration/backfill_ci_project_mirrors.rb'
- 'lib/gitlab/background_migration/backfill_ci_queuing_tables.rb'
- 'lib/gitlab/background_migration/fix_projects_without_prometheus_service.rb'
- 'lib/gitlab/background_migration/populate_latest_pipeline_ids.rb'
- 'lib/gitlab/ci/config/entry/includes.rb'
- 'lib/gitlab/ci/config/entry/trigger.rb'
- 'lib/gitlab/config/entry/validatable.rb'
- 'lib/gitlab/database/background_migration/batched_migration.rb'
- 'lib/gitlab/database/background_migration_job.rb'
- 'lib/gitlab/database/postgres_foreign_key.rb'
- 'lib/gitlab/database/postgres_index.rb'
- 'lib/gitlab/database/postgres_partition.rb'
- 'lib/gitlab/database/postgres_partitioned_table.rb'
- 'lib/gitlab/gl_repository.rb'
- 'lib/gitlab/import_export/import_failure_service.rb'
- 'lib/gitlab/merge_requests/commit_message_generator.rb'
- 'lib/gitlab/seeder.rb'
- 'lib/api/validations/types/comma_separated_to_array.rb'
- 'lib/api/validations/types/comma_separated_to_integer_array.rb'
- 'lib/api/validations/types/hash_of_integer_values.rb'
- 'lib/api/validations/validators/check_assignees_count.rb'
- 'lib/banzai/filter/ascii_doc_sanitization_filter.rb'
- 'lib/banzai/filter/base_sanitization_filter.rb'
- 'lib/banzai/filter/sanitization_filter.rb'
- 'lib/gitlab/action_cable/request_store_callbacks.rb'
- 'lib/gitlab/checks/diff_check.rb'
- 'lib/gitlab/database/load_balancing/action_cable_callbacks.rb'
- 'lib/gitlab/middleware/rack_multipart_tempfile_factory.rb'
- 'lib/gitlab/omniauth_initializer.rb'
- 'lib/gitlab/prometheus/queries/query_additional_metrics.rb'
- 'lib/gitlab/rack_attack.rb'
- 'lib/gitlab/sidekiq_config/worker_matcher.rb'
- 'lib/gitlab/sidekiq_signals.rb'
- 'lib/gitlab/utils/measuring.rb'
- 'lib/gitlab/visibility_level.rb'
- 'rubocop/cop/rspec/modify_sidekiq_middleware.rb'
- 'rubocop/cop/rspec/timecop_freeze.rb'
- 'rubocop/cop/rspec/timecop_travel.rb'
- 'lib/gitlab/sidekiq_middleware.rb'
- 'lib/gitlab/utils/usage_data.rb'
- 'qa/qa/page/base.rb'
- 'qa/qa/runtime/allure_report.rb'
- 'qa/qa/specs/features/api/1_manage/import/import_large_github_repo_spec.rb'
- 'qa/qa/support/api.rb'
- 'rubocop/cop/inject_enterprise_edition_module.rb'
- 'rubocop/cop/rspec/have_gitlab_http_status.rb'
- 'spec/controllers/concerns/routable_actions_spec.rb'
- 'spec/deprecation_toolkit_env.rb'
- 'spec/factories/design_management/designs.rb'
- 'spec/features/projects/issues/design_management/user_views_designs_with_svg_xss_spec.rb'
- 'spec/graphql/resolvers/concerns/resolves_groups_spec.rb'
- 'spec/features/groups/dependency_proxy_for_containers_spec.rb'
- 'spec/graphql/types/base_object_spec.rb'
- 'spec/lib/gitlab/action_cable/request_store_callbacks_spec.rb'
- 'spec/lib/gitlab/cross_project_access/class_methods_spec.rb'
- 'spec/lib/gitlab/database/consistency_spec.rb'
- 'spec/lib/gitlab/database/dynamic_model_helpers_spec.rb'
- 'spec/lib/gitlab/database/load_balancing/action_cable_callbacks_spec.rb'
- 'spec/lib/gitlab/database/load_balancing_spec.rb'
- 'spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb'
- 'spec/lib/gitlab/database/migration_helpers_spec.rb'
- 'spec/lib/gitlab/database/query_analyzers/gitlab_schemas_metrics_spec.rb'
- 'spec/lib/gitlab/graphql/tracers/application_context_tracer_spec.rb'
- 'spec/lib/gitlab/graphql/tracers/timer_tracer_spec.rb'
- 'spec/lib/gitlab/import_export/members_mapper_spec.rb'
- 'spec/lib/gitlab/sidekiq_middleware/size_limiter/validator_spec.rb'
- 'spec/migrations/20210722150102_operations_feature_flags_correct_flexible_rollout_values_spec.rb'
- 'spec/migrations/20210804150320_create_base_work_item_types_spec.rb'
- 'spec/migrations/20210819145000_drop_temporary_columns_and_triggers_for_ci_builds_runner_session_spec.rb'
- 'spec/migrations/20210831203408_upsert_base_work_item_types_spec.rb'
- 'spec/migrations/20210902144144_drop_temporary_columns_and_triggers_for_ci_build_needs_spec.rb'
- 'spec/migrations/20210906100316_drop_temporary_columns_and_triggers_for_ci_build_trace_chunks_spec.rb'
- 'spec/migrations/20210906130643_drop_temporary_columns_and_triggers_for_taggings_spec.rb'
- 'spec/migrations/20210907013944_cleanup_bigint_conversion_for_ci_builds_metadata_spec.rb'
- 'spec/migrations/20210915022415_cleanup_bigint_conversion_for_ci_builds_spec.rb'
- 'spec/migrations/20210922021816_drop_int4_columns_for_ci_job_artifacts_spec.rb'
- 'spec/migrations/20210922025631_drop_int4_column_for_ci_sources_pipelines_spec.rb'
- 'spec/migrations/20210922082019_drop_int4_column_for_events_spec.rb'
- 'spec/migrations/20210922091402_drop_int4_column_for_push_event_payloads_spec.rb'
- 'spec/migrations/20211126115449_encrypt_static_objects_external_storage_auth_token_spec.rb'
- 'spec/migrations/20211203091642_add_index_to_projects_on_marked_for_deletion_at_spec.rb'
- 'spec/migrations/20220120094340_drop_position_from_security_findings_spec.rb'
- 'spec/migrations/20220128155814_fix_approval_rules_code_owners_rule_type_index_spec.rb'
- 'spec/migrations/20220305223212_add_security_training_providers_spec.rb'
- 'spec/migrations/20220505174658_update_index_on_alerts_to_exclude_null_fingerprints_spec.rb'
- 'spec/migrations/generate_customers_dot_jwt_signing_key_spec.rb'
- 'spec/migrations/insert_ci_daily_pipeline_schedule_triggers_plan_limits_spec.rb'
- 'spec/migrations/recreate_index_security_ci_builds_on_name_and_id_parser_features_spec.rb'
- 'spec/migrations/recreate_index_security_ci_builds_on_name_and_id_parser_with_new_features_spec.rb'
- 'spec/migrations/remove_schedule_and_status_from_pending_alert_escalations_spec.rb'
- 'spec/models/ability_spec.rb'
- 'spec/models/broadcast_message_spec.rb'
- 'spec/models/concerns/participable_spec.rb'
- 'spec/lib/gitlab/middleware/rack_multipart_tempfile_factory_spec.rb'
- 'spec/lib/gitlab/path_regex_spec.rb'
- 'spec/services/groups/autocomplete_service_spec.rb'
- 'spec/services/notes/create_service_spec.rb'
- 'spec/services/issues/referenced_merge_requests_service_spec.rb'
- 'spec/services/projects/autocomplete_service_spec.rb'
- 'spec/services/projects/lfs_pointers/lfs_download_link_list_service_spec.rb'
- 'spec/support/helpers/email_helpers.rb'
- 'spec/support/helpers/reference_parser_helpers.rb'
- 'spec/support/shared_examples/lib/gitlab/middleware/read_only_gitlab_instance_shared_examples.rb'
- 'spec/support/shared_examples/quick_actions/issuable/issuable_quick_actions_shared_examples.rb'
- 'spec/workers/process_commit_worker_spec.rb'

View File

@ -270,6 +270,25 @@ export const secondsToMilliseconds = (seconds) => seconds * 1000;
*/
export const secondsToDays = (seconds) => Math.round(seconds / 86400);
/**
* Returns the date `n` seconds after the date provided
*
* @param {Date} date the initial date
* @param {Number} numberOfSeconds number of seconds after
* @return {Date} A `Date` object `n` seconds after the provided `Date`
*/
export const nSecondsAfter = (date, numberOfSeconds) =>
new Date(date.getTime() + numberOfSeconds * 1000);
/**
* Returns the date `n` seconds before the date provided
*
* @param {Date} date the initial date
* @param {Number} numberOfSeconds number of seconds before
* @return {Date} A `Date` object `n` seconds before the provided `Date`
*/
export const nSecondsBefore = (date, numberOfSeconds) => nSecondsAfter(date, -numberOfSeconds);
/**
* Returns the date `n` days after the date provided
*

View File

@ -11,13 +11,10 @@ export default {
render(h) {
const { extensions } = registeredExtensions;
if (extensions.length === 0) return null;
return h(
'section',
{
attrs: {
class: 'mr-section-container mr-widget-workflow',
role: 'region',
'aria-label': __('Merge request reports'),
},

View File

@ -0,0 +1,18 @@
<script>
export default {
data() {
return {
hasChildren: false,
};
},
updated() {
this.hasChildren = this.$scopedSlots.default?.()?.some((c) => c.tag);
},
};
</script>
<template>
<div v-show="hasChildren" class="mr-section-container mr-widget-workflow">
<slot></slot>
</div>
</template>

View File

@ -1,7 +1,10 @@
<script>
import { GlSafeHtmlDirective } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { registerExtension } from '~/vue_merge_request_widget/components/extensions';
import {
registerExtension,
registeredExtensions,
} from '~/vue_merge_request_widget/components/extensions';
import MrWidgetApprovals from 'ee_else_ce/vue_merge_request_widget/components/approvals/approvals.vue';
import MRWidgetService from 'ee_else_ce/vue_merge_request_widget/services/mr_widget_service';
import MRWidgetStore from 'ee_else_ce/vue_merge_request_widget/stores/mr_widget_store';
@ -47,6 +50,7 @@ import terraformExtension from './extensions/terraform';
import accessibilityExtension from './extensions/accessibility';
import codeQualityExtension from './extensions/code_quality';
import testReportExtension from './extensions/test_report';
import ReportWidgetContainer from './components/report_widget_container.vue';
export default {
// False positive i18n lint: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/25
@ -86,6 +90,7 @@ export default {
SecurityReportsApp: () => import('~/vue_shared/security_reports/security_reports_app.vue'),
MergeChecksFailed: () => import('./components/states/merge_checks_failed.vue'),
ReadyToMerge: ReadyToMergeState,
ReportWidgetContainer,
},
apollo: {
state: {
@ -216,6 +221,9 @@ export default {
return !this.mr.mergeDetailsCollapsed;
},
hasExtensions() {
return registeredExtensions.extensions.length;
},
},
watch: {
'mr.machineValue': {
@ -553,7 +561,17 @@ export default {
:mr="mr"
:service="service"
/>
<extensions-container :mr="mr" />
<report-widget-container>
<extensions-container v-if="hasExtensions" :mr="mr" />
<security-reports-app
v-if="shouldRenderSecurityReport && !shouldShowSecurityExtension"
:pipeline-id="mr.pipeline.id"
:project-id="mr.sourceProjectId"
:security-reports-docs-path="mr.securityReportsDocsPath"
:target-project-full-path="mr.targetProjectFullPath"
:mr-iid="mr.iid"
/>
</report-widget-container>
<div class="mr-section-container mr-widget-workflow">
<div v-if="hasAlerts" class="gl-overflow-hidden mr-widget-alert-container">
<mr-widget-alert-message
@ -582,15 +600,6 @@ export default {
<widget-container v-if="mr" :mr="mr" />
<security-reports-app
v-if="shouldRenderSecurityReport && !shouldShowSecurityExtension"
:pipeline-id="mr.pipeline.id"
:project-id="mr.sourceProjectId"
:security-reports-docs-path="mr.securityReportsDocsPath"
:target-project-full-path="mr.targetProjectFullPath"
:mr-iid="mr.iid"
/>
<div class="mr-widget-section" data-qa-selector="mr_widget_content">
<component :is="componentName" :mr="mr" :service="service" />
<ready-to-merge

View File

@ -1,5 +1,2 @@
.gl-spinner-container{ class: @class }
- if @inline
%span{ class: spinner_class, aria: {label: @label} }
- else
%div{ class: spinner_class, aria: {label: @label} }
= content_tag (@inline ? :span : :div), **html_options do
%span{ class: spinner_class, aria: {label: @label} }>

View File

@ -2,26 +2,31 @@
module Pajamas
class SpinnerComponent < Pajamas::Component
# @param [String] class
# @param [Symbol] color
# @param [Boolean] inline
# @param [String] label
# @param [Symbol] size
def initialize(class: '', color: :dark, inline: false, label: _("Loading"), size: :sm)
@class = binding.local_variable_get(:class)
def initialize(color: :dark, inline: false, label: _("Loading"), size: :sm, **html_options)
@color = filter_attribute(color.to_sym, COLOR_OPTIONS)
@inline = inline
@label = label.presence
@size = filter_attribute(size.to_sym, SIZE_OPTIONS)
@html_options = html_options
end
private
def spinner_class
["gl-spinner", "gl-spinner-#{@size}", "gl-spinner-#{@color}"]
["gl-spinner", "gl-spinner-#{@size}", "gl-spinner-#{@color} gl-vertical-align-text-bottom!"]
end
COLOR_OPTIONS = [:light, :dark].freeze
SIZE_OPTIONS = [:sm, :md, :lg, :xl].freeze
def html_options
options = format_options(options: @html_options, css_classes: "gl-spinner-container")
options[:role] = "status"
options
end
end
end

View File

@ -71,18 +71,13 @@ module IconsHelper
#
# See also https://gitlab-org.gitlab.io/gitlab-ui/?path=/story/base-loading-icon--default
def gl_loading_icon(inline: false, color: 'dark', size: 'sm', css_class: nil, data: nil)
spinner = content_tag(:span, "", {
class: %[gl-spinner gl-spinner-#{color} gl-spinner-#{size} gl-vertical-align-text-bottom!],
aria: { label: _('Loading') },
render Pajamas::SpinnerComponent.new(
inline: inline,
color: color,
size: size,
class: css_class,
data: data
})
container_classes = ['gl-spinner-container']
container_classes << css_class unless css_class.blank?
content_tag(inline ? :span : :div, spinner, {
class: container_classes,
role: 'status'
})
)
end
def external_snippet_icon(name)

View File

@ -11,6 +11,8 @@ module Emails
)
@recipient = User.find(user_id)
add_project_headers
mail_with_locale(
to: @recipient.notification_email_for(@project.group),
subject: subject(release_email_subject)

View File

@ -54,3 +54,4 @@
.col-12
.issuable-form-select-holder
= form.gitlab_ui_datepicker :due_date, placeholder: _('Select due date'), autocomplete: 'off', id: "issuable-due-date"
= render_if_exists "shared/issuable/form/iteration", form: form, group: project.group

View File

@ -566,7 +566,8 @@ module Gitlab
# Used in app/services/web_hooks/log_execution_service.rb: log_execution
ActiveSupport::TimeWithZone,
ActiveSupport::TimeZone,
Gitlab::Color # https://gitlab.com/gitlab-org/gitlab/-/issues/368844
Gitlab::Color, # https://gitlab.com/gitlab-org/gitlab/-/issues/368844,
Hashie::Array # https://gitlab.com/gitlab-org/gitlab/-/issues/378089
]
# on_master_start yields immediately in unclustered environments and runs

View File

@ -0,0 +1,8 @@
---
name: ci_variable_expansion_in_rules_exists
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/101639
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/381046
milestone: '15.6'
type: development
group: group::pipeline authoring
default_enabled: false

View File

@ -5,31 +5,10 @@ return unless Gitlab::Utils.to_boolean(ENV['GITLAB_MEMORY_WATCHDOG_ENABLED'])
Gitlab::Cluster::LifecycleEvents.on_worker_start do
watchdog = Gitlab::Memory::Watchdog.new
max_strikes = ENV.fetch('GITLAB_MEMWD_MAX_STRIKES', 5).to_i
sleep_time_seconds = ENV.fetch('GITLAB_MEMWD_SLEEP_TIME_SEC', 60).to_i
max_mem_growth = ENV.fetch('GITLAB_MEMWD_MAX_MEM_GROWTH', 3.0).to_f
max_heap_frag = ENV.fetch('GITLAB_MEMWD_MAX_HEAP_FRAG', 0.5).to_f
watchdog.configure do |config|
config.handler =
if Gitlab::Runtime.puma?
Gitlab::Memory::Watchdog::PumaHandler.new
elsif Gitlab::Runtime.sidekiq?
Gitlab::Memory::Watchdog::TermProcessHandler.new
else
Gitlab::Memory::Watchdog::NullHandler.instance
end
config.logger = Gitlab::AppLogger
config.sleep_time_seconds = sleep_time_seconds
# config.monitor.use MonitorClass, args*, max_strikes:, kwargs**, &block
config.monitors.use Gitlab::Memory::Watchdog::Monitor::HeapFragmentation,
max_heap_fragmentation: max_heap_frag,
max_strikes: max_strikes
config.monitors.use Gitlab::Memory::Watchdog::Monitor::UniqueMemoryGrowth,
max_mem_growth: max_mem_growth,
max_strikes: max_strikes
if Gitlab::Runtime.puma?
watchdog.configure(&Gitlab::Memory::Watchdog::Configurator.configure_for_puma)
elsif Gitlab::Runtime.sidekiq?
watchdog.configure(&Gitlab::Memory::Watchdog::Configurator.configure_for_sidekiq)
end
Gitlab::BackgroundTask.new(watchdog).start

View File

@ -17,6 +17,8 @@ metadata:
# Keep in alphabetical order
- name: access_requests
description: Operations related to access requests
- name: ci_variables
description: Operations related to CI/CD variables
- name: cluster_agents
description: Operations related to the GitLab agent for Kubernetes
- name: ci_resource_groups
@ -51,6 +53,8 @@ metadata:
description: Operations related to import BitBucket projects
- name: project_import_github
description: Operations related to import GitHub projects
- name: protected environments
description: Operations related to protected environments
- name: release_links
description: Operations related to release assets (links)
- name: releases
@ -60,4 +64,4 @@ metadata:
- name: system_hooks
description: Operations related to system hooks
- name: unleash_api
description: Operations related to Unleash API
description: Operations related to Unleash API

View File

@ -10333,7 +10333,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="boardepicchildreniidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. |
| <a id="boardepicchildreniids"></a>`iids` | [`[ID!]`](#id) | List of IIDs of epics, e.g., `[1, 2]`. |
| <a id="boardepicchildrenin"></a>`in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
| <a id="boardepicchildrenincludeancestorgroups"></a>`includeAncestorGroups` | [`Boolean`](#boolean) | Include epics from ancestor groups. |
| <a id="boardepicchildrenincludeancestorgroups"></a>`includeAncestorGroups` | [`Boolean`](#boolean) | Include child epics from ancestor groups. |
| <a id="boardepicchildrenincludedescendantgroups"></a>`includeDescendantGroups` | [`Boolean`](#boolean) | Include epics from descendant groups. |
| <a id="boardepicchildrenlabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. |
| <a id="boardepicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
@ -12247,7 +12247,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="epicchildreniidstartswith"></a>`iidStartsWith` | [`String`](#string) | Filter epics by IID for autocomplete. |
| <a id="epicchildreniids"></a>`iids` | [`[ID!]`](#id) | List of IIDs of epics, e.g., `[1, 2]`. |
| <a id="epicchildrenin"></a>`in` | [`[IssuableSearchableField!]`](#issuablesearchablefield) | Specify the fields to perform the search in. Defaults to `[TITLE, DESCRIPTION]`. Requires the `search` argument.'. |
| <a id="epicchildrenincludeancestorgroups"></a>`includeAncestorGroups` | [`Boolean`](#boolean) | Include epics from ancestor groups. |
| <a id="epicchildrenincludeancestorgroups"></a>`includeAncestorGroups` | [`Boolean`](#boolean) | Include child epics from ancestor groups. |
| <a id="epicchildrenincludedescendantgroups"></a>`includeDescendantGroups` | [`Boolean`](#boolean) | Include epics from descendant groups. |
| <a id="epicchildrenlabelname"></a>`labelName` | [`[String!]`](#string) | Filter epics by labels. |
| <a id="epicchildrenmilestonetitle"></a>`milestoneTitle` | [`String`](#string) | Filter epics by milestone title, computed from epic's issues. |
@ -17693,7 +17693,7 @@ four standard [pagination arguments](#connection-pagination-arguments):
| <a id="projectworkitemssearch"></a>`search` | [`String`](#string) | Search query for title or description. |
| <a id="projectworkitemssort"></a>`sort` | [`WorkItemSort`](#workitemsort) | Sort work items by this criteria. |
| <a id="projectworkitemsstate"></a>`state` | [`IssuableState`](#issuablestate) | Current state of this work item. |
| <a id="projectworkitemsstatuswidget"></a>`statusWidget` | [`StatusFilterInput`](#statusfilterinput) | Input for status widget filter. |
| <a id="projectworkitemsstatuswidget"></a>`statusWidget` | [`StatusFilterInput`](#statusfilterinput) | Input for status widget filter. Ignored if `work_items_mvc_2` is disabled. |
| <a id="projectworkitemstypes"></a>`types` | [`[IssueType!]`](#issuetype) | Filter work items by the given work item types. |
### `ProjectCiCdSetting`

View File

@ -95,7 +95,7 @@ Example response:
}
```
## Protect an environment
## Protect a single environment
Protects a single environment.
@ -136,7 +136,7 @@ Example response:
}
```
## Update an environment
## Update a protected environment
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/351854) in GitLab 15.4.
@ -304,7 +304,7 @@ Example response:
}
```
## Unprotect environment
## Unprotect a single environment
Unprotects the given protected environment.

View File

@ -103,7 +103,7 @@ Example response:
}
```
## Protect repository environments
## Protect a single environment
Protects a single environment:
@ -343,7 +343,7 @@ Example response:
}
```
## Unprotect environment
## Unprotect a single environment
Unprotects the given protected environment:

View File

@ -66,7 +66,7 @@ There are two ways to configure approvals for a protected environment:
1. Using the [UI](protected_environments.md#protecting-environments)
1. Set the **Required approvals** field to 1 or more.
1. Using the [REST API](../../api/protected_environments.md#protect-repository-environments)
1. Using the [REST API](../../api/protected_environments.md#protect-a-single-environment)
2. Set the `required_approval_count` field to 1 or more.
After this is configured, all jobs deploying to this environment automatically go into a blocked state and wait for approvals before running. Ensure that the number of required approvals is less than the number of users allowed to deploy.
@ -89,7 +89,7 @@ Maintainer role.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345678) in GitLab 14.10 with a flag named `deployment_approval_rules`. Disabled by default.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/345678) in GitLab 15.0. [Feature flag `deployment_approval_rules`](https://gitlab.com/gitlab-org/gitlab/-/issues/345678) removed.
1. Using the [REST API](../../api/group_protected_environments.md#protect-an-environment).
1. Using the [REST API](../../api/group_protected_environments.md#protect-a-single-environment).
1. `deploy_access_levels` represents which entity can execute the deployment job.
1. `approval_rules` represents which entity can approve the deployment job.

View File

@ -36,6 +36,7 @@ There are two places defined variables can be used. On the:
| [`include`](../yaml/index.md#include) | yes | GitLab | The variable expansion is made by the [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism) in GitLab. <br/><br/>See [Use variables with include](../yaml/includes.md#use-variables-with-include) for more information on supported variables. |
| [`only:variables`](../yaml/index.md#onlyvariables--exceptvariables) | no | Not applicable | The variable must be in the form of `$variable`. Not supported are the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
| [`resource_group`](../yaml/index.md#resource_group) | yes | GitLab | Similar to `environment:url`, but the variables expansion doesn't support the following:<br/>- `CI_ENVIRONMENT_URL`<br/>- [Persisted variables](#persisted-variables). |
| [`rules:exists`](../yaml/index.md#rulesexists) | yes | GitLab | The variable expansion is made by the [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism) in GitLab. |
| [`rules:if`](../yaml/index.md#rulesif) | no | Not applicable | The variable must be in the form of `$variable`. Not supported are the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
| [`script`](../yaml/index.md#script) | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment). |
| [`services:name`](../yaml/index.md#services) | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism). |

View File

@ -3437,7 +3437,8 @@ relative to `refs/heads/branch1` and the pipeline source is a merge request even
#### `rules:exists`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24021) in GitLab 12.4.
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/24021) in GitLab 12.4.
> - CI/CD variable support [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/283881) in GitLab 15.6 [with a flag](../../administration/feature_flags.md) named `ci_variable_expansion_in_rules_exists`. Disabled by default.
Use `exists` to run a job when certain files exist in the repository.
@ -3445,8 +3446,7 @@ Use `exists` to run a job when certain files exist in the repository.
**Possible inputs**:
- An array of file paths. Paths are relative to the project directory (`$CI_PROJECT_DIR`)
and can't directly link outside it. File paths can use glob patterns.
- An array of file paths. Paths are relative to the project directory (`$CI_PROJECT_DIR`) and can't directly link outside it. File paths can use glob patterns and [CI/CD variables](../variables/where_variables_can_be_used.md#gitlab-ciyml-file).
**Example of `rules:exists`**:

View File

@ -159,3 +159,232 @@ export default {
[In Vue 3](https://v3-migration.vuejs.org/breaking-changes/props-default-this.html),
the props default value factory is passed the raw props as an argument, and can
also access injections.
## Handling libraries that do not work with `@vue/compat`
**Problem**
Some libraries rely on Vue.js 2 internals. They might not work with `@vue/compat`, so we need a strategy to use an updated version with Vue.js 3 while maintaining compatibility with the current codebase.
**Goals**
- We should add as few changes as possible to existing code to support new libraries. Instead, we should **add*- new code, which will act as **facade**, making the new version compatible with the old one
- Switching between new and old versions should be hidden inside tooling (webpack / jest) and should not be exposed to the code
- All facades specific to migration should live in the same directory to simplify future migration steps
### Step-by-step migration
In the step-by-step guide, we will be migrating [VueApollo Demo](https://gitlab.com/gitlab-org/frontend/vue3-migration-vue-apollo/-/tree/main/src/vue3compat) project. It will allow us to focus on migration specifics while avoiding nuances of complex tooling setup in the GitLab project. The project intentionally uses the same tooling as GitLab:
- webpack
- yarn
- Vue.js + VueApollo
#### Initial state
Right after cloning, you could run [VueApollo Demo](https://gitlab.com/gitlab-org/frontend/vue3-migration-vue-apollo/-/tree/main/src/vue3compat) with Vue.js 2 using `yarn serve` or with Vue.js 3 (compat build) using `yarn serve:vue3`. However latter immediately crashes:
```javascript
Uncaught TypeError: Cannot read properties of undefined (reading 'loading')
```
VueApollo v3 (used for Vue.js 2) fails to initialize in Vue.js compat
NOTE:
While stubbing `Vue.version` will solve VueApollo-related issues in the demo project, it will still lose reactivity on specific scenarios, so an upgrade is still needed
#### Step 1. Perform upgrade according to library docs
According to [VueApollo v4 installation guide](https://v4.apollo.vuejs.org/guide/installation.html), we need to install `@vue/apollo-option` (this package provides VueApollo support for Options API) and make changes to our application:
```diff
--- a/src/index.js
+++ b/src/index.js
@@ -1,19 +1,17 @@
-import Vue from "vue";
-import VueApollo from "vue-apollo";
+import { createApp, h } from "vue";
+import { createApolloProvider } from "@vue/apollo-option";
import Demo from "./components/Demo.vue";
import createDefaultClient from "./lib/graphql";
-Vue.use(VueApollo);
-
-const apolloProvider = new VueApollo({
+const apolloProvider = createApolloProvider({
defaultClient: createDefaultClient(),
});
-new Vue({
- el: "#app",
- apolloProvider,
- render(h) {
+const app = createApp({
+ render() {
return h(Demo);
},
});
+app.use(apolloProvider);
+app.mount("#app");
```
You can view these changes in [01-upgrade-vue-apollo](https://gitlab.com/gitlab-org/frontend/vue3-migration-vue-apollo/-/compare/main...01-upgrade-vue-apollo) branch of demo project
#### Step 2. Addressing differences in augmenting applications in Vue.js 2 and 3
In Vue.js 2 tooling like `VueApollo` is initialized in a "lazy" fashion:
```javascript
// We are registering VueApollo "handler" to handle some data LATER
Vue.use(VueApollo)
// ...
// apolloProvider is provided at app instantiation,
// previously registered VueApollo will handle that
new Vue({ /- ... */, apolloProvider })
```
In Vue.js 3 both steps were merged in one - we are immediately registering the handler and passing configuration:
```javascript
app.use(apolloProvider)
```
In order to backport this behavior, we need the following knowledge:
- We can access extra options provided to Vue instance via `$options`, so extra `apolloProvider` will be visible as `this.$options.apolloProvider`
- We can access the current `app` (in Vue.js 3 meaning) on the Vue instance via `this.$.appContext.app`
NOTE:
We're relying on non-public Vue.js 3 API in this case. However, since `@vue/compat` builds are expected to be available only for 3.2.x branch, we have reduced risks that this API will be changed
With this knowledge, we can move the initialization of our tooling as early as possible in Vue2 - in the `beforeCreate()` lifecycle hook:
```diff
--- a/src/index.js
+++ b/src/index.js
@@ -1,4 +1,4 @@
-import { createApp, h } from "vue";
+import Vue from "vue";
import { createApolloProvider } from "@vue/apollo-option";
import Demo from "./components/Demo.vue";
@@ -8,10 +8,13 @@ const apolloProvider = createApolloProvider({
defaultClient: createDefaultClient(),
});
-const app = createApp({
- render() {
+new Vue({
+ el: "#app",
+ apolloProvider,
+ render(h) {
return h(Demo);
},
+ beforeCreate() {
+ this.$.appContext.app.use(this.$options.apolloProvider);
+ },
});
-app.use(apolloProvider);
-app.mount("#app");
```
You can view these changes in [02-bring-back-new-vue](https://gitlab.com/gitlab-org/frontend/vue3-migration-vue-apollo/-/compare/01-upgrade-vue-apollo...02-bring-back-new-vue) branch of demo project
#### Step 3. Recreating `VueApollo` class
Vue.js 3 libraries (and Vue.js itself) have a preference for using factories like `createApp` instead of classes (previously `new Vue`)
`VueApollo` class served two purposes:
- constructor for creating `apolloProvider`
- installation of apollo-related logic in components
We can utilize `Vue.use(VueApollo)` code, which existed in our codebase, to hide there our mixin and avoid modification of our app code:
```diff
--- a/src/index.js
+++ b/src/index.js
@@ -4,7 +4,26 @@ import { createApolloProvider } from "@vue/apollo-option";
import Demo from "./components/Demo.vue";
import createDefaultClient from "./lib/graphql";
-const apolloProvider = createApolloProvider({
+class VueApollo {
+ constructor(...args) {
+ return createApolloProvider(...args);
+ }
+
+ // called by Vue.use
+ static install() {
+ Vue.mixin({
+ beforeCreate() {
+ if (this.$options.apolloProvider) {
+ this.$.appContext.app.use(this.$options.apolloProvider);
+ }
+ },
+ });
+ }
+}
+
+Vue.use(VueApollo);
+
+const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
@@ -14,7 +33,4 @@ new Vue({
render(h) {
return h(Demo);
},
- beforeCreate() {
- this.$.appContext.app.use(this.$options.apolloProvider);
- },
});
```
You can view these changes in [03-recreate-vue-apollo](https://gitlab.com/gitlab-org/frontend/vue3-migration-vue-apollo/-/compare/02-bring-back-new-vue...03-recreate-vue-apollo) branch of demo project
#### Step 4. Moving `VueApollo` class to a separate file and setting up an alias
Now, we have almost the same code (excluding import) as in Vue.js 2 version.
We will move our facade to the separate file and set up `webpack` conditionally execute it if `vue-apollo` is imported when using Vue.js 3:
```diff
--- a/src/index.js
+++ b/src/index.js
@@ -1,5 +1,5 @@
import Vue from "vue";
-import { createApolloProvider } from "@vue/apollo-option";
+import VueApollo from "vue-apollo";
import Demo from "./components/Demo.vue";
import createDefaultClient from "./lib/graphql";
diff --git a/webpack.config.js b/webpack.config.js
index 6160d3f..b8b955f 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -12,6 +12,7 @@ if (USE_VUE3) {
VUE3_ALIASES = {
vue: "@vue/compat",
+ "vue-apollo": path.resolve("src/vue3compat/vue-apollo"),
};
}
```
(moving `VueApollo` class from `index.js` to `vue3compat/vue-apollo.js` as default export is omitted for clarity)
You can view these changes in [04-add-webpack-alias](https://gitlab.com/gitlab-org/frontend/vue3-migration-vue-apollo/-/compare/03-recreate-vue-apollo...04-add-webpack-alias) branch of demo project
#### Step 5. Observe the results
At this point, you should be able again to run **both*- Vue.js 2 version with `yarn serve` and Vue.js 3 one with `yarn serve:vue3`
[Final MR](https://gitlab.com/gitlab-org/frontend/vue3-migration-vue-apollo/-/merge_requests/1/diffs) with all changes from previous steps displays no changes to `index.js` (application code), which was our goal
### Applying this approach in the GitLab project
In [commit adding VueApollo v4 support](https://gitlab.com/gitlab-org/gitlab/-/commit/e0af7e6479695a28a4fe85a88f90815aa3ce2814) we can see additional nuances not covered by step-by-step guide:
- We might need to add additional imports to our facades (our code in GitLab uses `ApolloMutation` component)
- We need to update aliases not only for webpack but also for jest so our tests could also consume our facade

View File

@ -103,31 +103,6 @@ When you upload an image, you can add text to the image and link it to the origi
If you add a link, it is shown above the uploaded image.
#### View an alert's logs
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/201846) in GitLab Ultimate 12.8.
> - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/217768) in GitLab 13.3.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/25455) from GitLab Ultimate to GitLab Free in 12.9.
Viewing logs from a metrics panel can be useful if you're triaging an
application incident and need to [explore logs](../metrics/dashboards/index.md#chart-context-menu)
from across your application. These logs help you understand what's affecting
your application's performance and how to resolve any problems.
Prerequisite:
- You must have at least the Developer role.
To view the logs for an alert:
1. On the top bar, select **Main menu > Projects** and find your project.
1. On the left sidebar, select **Monitor > Alerts**.
1. Select the alert you want to view.
1. Below the title of the alert, select the **Metrics** tab.
1. Select the [menu](../metrics/dashboards/index.md#chart-context-menu) of
the metric chart to view options.
1. Select **View logs**.
### Activity feed tab
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1.
@ -177,6 +152,16 @@ To change an alert's status:
To stop email notifications for alert reoccurrences in projects with [email notifications enabled](paging.md#email-notifications-for-alerts),
change the alert's status away from **Triggered**.
#### Resolve an alert by closing the linked incident
Prerequisites:
- You must have at least the Reporter role.
When you close an [incident](incidents.md) that is linked to an alert,
the linked alert's status changes to **Resolved**.
You are then credited with the alert's status change.
#### As an on-call responder **(PREMIUM)**
On-call responders can respond to [alert pages](paging.md#escalating-an-alert)

View File

@ -207,7 +207,8 @@ the appropriate project and followed up from there.
### Fields in the new issue form
> Adding the new issue to an epic [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13847) in GitLab 13.1.
> - Adding the new issue to an epic [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/13847) in GitLab 13.1.
> - Iteration field [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/233517) in GitLab 15.6.
When you're creating a new issue, you can complete the following fields:
@ -222,6 +223,7 @@ When you're creating a new issue, you can complete the following fields:
- [Due date](due_dates.md)
- [Milestone](../milestones/index.md)
- [Labels](../labels.md)
- [Iteration](../../group/iterations/index.md)
## Edit an issue

View File

@ -13,8 +13,9 @@ module API
namespace 'admin' do
namespace 'ci' do
namespace 'variables' do
desc 'Get instance-level variables' do
desc 'List all instance-level variables' do
success Entities::Ci::Variable
tags %w[ci_variables]
end
params do
use :pagination
@ -25,11 +26,13 @@ module API
present paginate(variables), with: Entities::Ci::Variable
end
desc 'Get a specific variable from a group' do
desc 'Get the details of a specific instance-level variable' do
success Entities::Ci::Variable
failure [{ code: 404, message: 'Instance Variable Not Found' }]
tags %w[ci_variables]
end
params do
requires :key, type: String, desc: 'The key of the variable'
requires :key, type: String, desc: 'The key of a variable'
end
get ':key' do
key = params[:key]
@ -42,16 +45,18 @@ module API
desc 'Create a new instance-level variable' do
success Entities::Ci::Variable
failure [{ code: 400, message: '400 Bad Request' }]
tags %w[ci_variables]
end
route_setting :log_safety, { safe: %w[key], unsafe: %w[value] }
params do
requires :key,
type: String,
desc: 'The key of the variable'
desc: 'The key of the variable. Max 255 characters'
requires :value,
type: String,
desc: 'The value of the variable'
desc: 'The value of a variable'
optional :protected,
type: String,
@ -64,7 +69,7 @@ module API
optional :variable_type,
type: String,
values: ::Ci::InstanceVariable.variable_types.keys,
desc: 'The type of variable, must be one of env_var or file. Defaults to env_var'
desc: 'The type of a variable. Available types are: env_var (default) and file'
end
post '/' do
variable_params = declared_params(include_missing: false)
@ -78,18 +83,20 @@ module API
end
end
desc 'Update an existing instance-variable' do
desc 'Update an instance-level variable' do
success Entities::Ci::Variable
failure [{ code: 404, message: 'Instance Variable Not Found' }]
tags %w[ci_variables]
end
route_setting :log_safety, { safe: %w[key], unsafe: %w[value] }
params do
optional :key,
type: String,
desc: 'The key of the variable'
desc: 'The key of a variable'
optional :value,
type: String,
desc: 'The value of the variable'
desc: 'The value of a variable'
optional :protected,
type: String,
@ -102,7 +109,7 @@ module API
optional :variable_type,
type: String,
values: ::Ci::InstanceVariable.variable_types.keys,
desc: 'The type of variable, must be one of env_var or file'
desc: 'The type of a variable. Available types are: env_var (default) and file'
end
put ':key' do
variable = ::Ci::InstanceVariable.find_by_key(params[:key])
@ -120,9 +127,11 @@ module API
desc 'Delete an existing instance-level variable' do
success Entities::Ci::Variable
failure [{ code: 404, message: 'Instance Variable Not Found' }]
tags %w[ci_variables]
end
params do
requires :key, type: String, desc: 'The key of the variable'
requires :key, type: String, desc: 'The key of a variable'
end
delete ':key' do
variable = ::Ci::InstanceVariable.find_by_key(params[:key])

View File

@ -171,6 +171,7 @@ module API
namespace do
# Keep in alphabetical order
mount ::API::AccessRequests
mount ::API::Admin::Ci::Variables
mount ::API::Appearance
mount ::API::Applications
mount ::API::BroadcastMessages
@ -180,6 +181,7 @@ module API
mount ::API::Ci::Runners
mount ::API::Clusters::AgentTokens
mount ::API::Clusters::Agents
mount ::API::CommitStatuses
mount ::API::DeployKeys
mount ::API::DeployTokens
mount ::API::Deployments
@ -217,7 +219,6 @@ module API
# Keep in alphabetical order
mount ::API::Admin::BatchedBackgroundMigrations
mount ::API::Admin::Ci::Variables
mount ::API::Admin::InstanceClusters
mount ::API::Admin::PlanLimits
mount ::API::Admin::Sidekiq

View File

@ -16,14 +16,20 @@ module API
before { authenticate! }
desc "Get a commit's statuses" do
success Entities::CommitStatus
success code: 200, model: Entities::CommitStatus
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
is_array true
end
params do
requires :sha, type: String, desc: 'The commit hash'
optional :ref, type: String, desc: 'The ref'
optional :stage, type: String, desc: 'The stage'
optional :name, type: String, desc: 'The name'
optional :all, type: String, desc: 'Show all statuses, default: false'
requires :sha, type: String, desc: 'The commit hash', documentation: { example: '18f3e63d05582537db6d183d9d557be09e1f90c8' }
optional :ref, type: String, desc: 'The ref', documentation: { example: 'develop' }
optional :stage, type: String, desc: 'The stage', documentation: { example: 'test' }
optional :name, type: String, desc: 'The name', documentation: { example: 'bundler:audit' }
optional :all, type: Boolean, desc: 'Show all statuses', documentation: { default: false }
use :pagination
end
# rubocop: disable CodeReuse/ActiveRecord
@ -43,19 +49,32 @@ module API
# rubocop: enable CodeReuse/ActiveRecord
desc 'Post status to a commit' do
success Entities::CommitStatus
success code: 200, model: Entities::CommitStatus
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not found' }
]
end
params do
requires :sha, type: String, desc: 'The commit hash'
requires :state, type: String, desc: 'The state of the status',
values: %w(pending running success failed canceled)
optional :ref, type: String, desc: 'The ref'
optional :target_url, type: String, desc: 'The target URL to associate with this status'
optional :description, type: String, desc: 'A short description of the status'
optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"', documentation: { default: 'default' }
optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems. Default: "default"', documentation: { default: 'default' }
optional :coverage, type: Float, desc: 'The total code coverage'
optional :pipeline_id, type: Integer, desc: 'An existing pipeline ID, when multiple pipelines on the same commit SHA have been triggered'
requires :sha, type: String, desc: 'The commit hash',
documentation: { example: '18f3e63d05582537db6d183d9d557be09e1f90c8' }
requires :state, type: String, desc: 'The state of the status',
values: %w(pending running success failed canceled),
documentation: { example: 'pending' }
optional :ref, type: String, desc: 'The ref',
documentation: { example: 'develop' }
optional :target_url, type: String, desc: 'The target URL to associate with this status',
documentation: { example: 'https://gitlab.example.com/thedude/gitlab-foss/builds/91' }
optional :description, type: String, desc: 'A short description of the status'
optional :name, type: String, desc: 'A string label to differentiate this status from the status of other systems',
documentation: { example: 'coverage', default: 'default' }
optional :context, type: String, desc: 'A string label to differentiate this status from the status of other systems',
documentation: { example: 'coverage', default: 'default' }
optional :coverage, type: Float, desc: 'The total code coverage',
documentation: { example: 100.0 }
optional :pipeline_id, type: Integer, desc: 'An existing pipeline ID, when multiple pipelines on the same commit SHA have been triggered'
end
# rubocop: disable CodeReuse/ActiveRecord
post ':id/statuses/:sha' do

View File

@ -4,10 +4,15 @@ module API
module Entities
module Ci
class Variable < Grape::Entity
expose :variable_type, :key, :value
expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) }
expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) }
expose :environment_scope, if: -> (entity, _) { entity.respond_to?(:environment_scope) }
expose :variable_type, documentation: { type: 'string', example: 'env_var' }
expose :key, documentation: { type: 'string', example: 'TEST_VARIABLE_1' }
expose :value, documentation: { type: 'string', example: 'TEST_1' }
expose :protected?, as: :protected, if: -> (entity, _) { entity.respond_to?(:protected?) },
documentation: { type: 'boolean' }
expose :masked?, as: :masked, if: -> (entity, _) { entity.respond_to?(:masked?) },
documentation: { type: 'boolean' }
expose :environment_scope, if: -> (entity, _) { entity.respond_to?(:environment_scope) },
documentation: { type: 'string', example: '*' }
end
end
end

View File

@ -3,8 +3,22 @@
module API
module Entities
class CommitStatus < Grape::Entity
expose :id, :sha, :ref, :status, :name, :target_url, :description,
:created_at, :started_at, :finished_at, :allow_failure, :coverage
expose :id, documentation: { type: 'integer', example: 93 }
expose :sha, documentation: { type: 'string', example: '18f3e63d05582537db6d183d9d557be09e1f90c8' }
expose :ref, documentation: { type: 'string', example: 'develop' }
expose :status, documentation: { type: 'string', example: 'success' }
expose :name, documentation: { type: 'string', example: 'default' }
expose :target_url, documentation: {
type: 'string',
example: 'https://gitlab.example.com/thedude/gitlab-foss/builds/91'
}
expose :description, documentation: { type: 'string' }
expose :created_at, documentation: { type: 'dateTime', example: '2016-01-19T09:05:50.355Z' }
expose :started_at, documentation: { type: 'dateTime', example: '2016-01-20T08:40:25.832Z' }
expose :finished_at, documentation: { type: 'dateTime', example: '2016-01-21T08:40:25.832Z' }
expose :allow_failure, documentation: { type: 'boolean', example: false }
expose :coverage, documentation: { type: 'number', format: 'float', example: 98.29 }
expose :author, using: Entities::UserBasic
end
end

View File

@ -3,8 +3,8 @@
module API
module Entities
class CustomAttribute < Grape::Entity
expose :key
expose :value
expose :key, documentation: { type: 'string', example: 'foo' }
expose :value, documentation: { type: 'string', example: 'bar' }
end
end
end

View File

@ -9,8 +9,17 @@ module API
user.avatar_url(only_path: false)
end
expose :avatar_path, if: ->(user, options) { options.fetch(:only_path, false) && user.avatar_path }
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes
expose(
:avatar_path,
documentation: {
type: 'string',
example: '/user/avatar/28/The-Big-Lebowski-400-400.png'
},
if: ->(user, options) { options.fetch(:only_path, false) && user.avatar_path }
)
expose :custom_attributes, using: 'API::Entities::CustomAttribute', if: :with_custom_attributes,
documentation: { is_array: true }
expose :web_url, documentation: { type: 'string', example: 'https://gitlab.example.com/root' } do |user, options|
Gitlab::Routing.url_helpers.user_url(user)

View File

@ -9,20 +9,34 @@ module Gitlab
MAX_PATTERN_COMPARISONS = 10_000
def initialize(globs)
globs = Array(globs)
@top_level_only = globs.all?(&method(:top_level_glob?))
@exact_globs, @pattern_globs = globs.partition(&method(:exact_glob?))
@globs = Array(globs)
@top_level_only = @globs.all?(&method(:top_level_glob?))
end
def satisfied_by?(_pipeline, context)
paths = worktree_paths(context)
exact_globs, pattern_globs = separate_globs(context)
exact_matches?(paths) || pattern_matches?(paths)
exact_matches?(paths, exact_globs) || pattern_matches?(paths, pattern_globs)
end
private
def separate_globs(context)
if ::Feature.enabled?(:ci_variable_expansion_in_rules_exists, context.project)
expanded_globs = expand_globs(context)
expanded_globs.partition(&method(:exact_glob?))
else
@globs.partition(&method(:exact_glob?))
end
end
def expand_globs(context)
@globs.map do |glob|
ExpandVariables.expand_existing(glob, -> { context.variables_hash })
end
end
def worktree_paths(context)
return [] unless context.project
@ -33,13 +47,16 @@ module Gitlab
end
end
def exact_matches?(paths)
@exact_globs.any? { |glob| paths.bsearch { |path| glob <=> path } }
def exact_matches?(paths, exact_globs)
exact_globs.any? do |glob|
paths.bsearch { |path| glob <=> path }
end
end
def pattern_matches?(paths)
def pattern_matches?(paths, pattern_globs)
comparisons = 0
@pattern_globs.any? do |glob|
pattern_globs.any? do |glob|
paths.any? do |path|
comparisons += 1
comparisons > MAX_PATTERN_COMPARISONS || pattern_match?(glob, path)

View File

@ -54,6 +54,17 @@ module Gitlab
init_prometheus_metrics
end
##
# Configuration for Watchdog, use like:
#
# watchdog.configure do |config|
# config.handler = Gitlab::Memory::Watchdog::TermProcessHandler
# config.sleep_time_seconds = 60
# config.logger = Gitlab::AppLogger
# config.monitors do |stack|
# stack.push MyMonitorClass, args*, max_strikes:, kwargs**, &block
# end
# end
def configure
yield @configuration
end

View File

@ -9,7 +9,7 @@ module Gitlab
@monitors = []
end
def use(monitor_class, *args, **kwargs, &block)
def push(monitor_class, *args, **kwargs, &block)
remove(monitor_class)
@monitors.push(build_monitor_state(monitor_class, *args, **kwargs, &block))
end
@ -39,11 +39,12 @@ module Gitlab
DEFAULT_SLEEP_TIME_SECONDS = 60
attr_reader :monitors
attr_writer :logger, :handler, :sleep_time_seconds
def initialize
@monitors = MonitorStack.new
def monitors
@monitor_stack ||= MonitorStack.new
yield @monitor_stack if block_given?
@monitor_stack
end
def handler

View File

@ -0,0 +1,63 @@
# frozen_string_literal: true
module Gitlab
module Memory
class Watchdog
class Configurator
class << self
def configure_for_puma
lambda do |config|
sleep_time_seconds = ENV.fetch('GITLAB_MEMWD_SLEEP_TIME_SEC', 60).to_i
config.logger = Gitlab::AppLogger
config.handler = Gitlab::Memory::Watchdog::PumaHandler.new
config.sleep_time_seconds = sleep_time_seconds
config.monitors(&configure_monitors_for_puma)
end
end
def configure_for_sidekiq
lambda do |config|
sleep_time_seconds = [ENV.fetch('SIDEKIQ_MEMORY_KILLER_CHECK_INTERVAL', 3).to_i, 2].max
config.logger = Sidekiq.logger
config.handler = Gitlab::Memory::Watchdog::TermProcessHandler.new
config.sleep_time_seconds = sleep_time_seconds
config.monitors(&configure_monitors_for_sidekiq)
end
end
private
def configure_monitors_for_puma
lambda do |stack|
max_strikes = ENV.fetch('GITLAB_MEMWD_MAX_STRIKES', 5).to_i
if Gitlab::Utils.to_boolean(ENV['DISABLE_PUMA_WORKER_KILLER'])
max_heap_frag = ENV.fetch('GITLAB_MEMWD_MAX_HEAP_FRAG', 0.5).to_f
max_mem_growth = ENV.fetch('GITLAB_MEMWD_MAX_MEM_GROWTH', 3.0).to_f
# stack.push MonitorClass, args*, max_strikes:, kwargs**, &block
stack.push Gitlab::Memory::Watchdog::Monitor::HeapFragmentation,
max_heap_fragmentation: max_heap_frag,
max_strikes: max_strikes
stack.push Gitlab::Memory::Watchdog::Monitor::UniqueMemoryGrowth,
max_mem_growth: max_mem_growth,
max_strikes: max_strikes
else
memory_limit = ENV.fetch('PUMA_WORKER_MAX_MEMORY', 1200).to_i
stack.push Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit,
memory_limit: memory_limit,
max_strikes: max_strikes
end
end
end
def configure_monitors_for_sidekiq
# NOP - At the moment we don't run watchdog for Sidekiq
end
end
end
end
end
end

View File

@ -22,7 +22,7 @@ module Gitlab
def call
heap_fragmentation = Gitlab::Metrics::Memory.gc_heap_fragmentation
return { threshold_violated: false, payload: {} } unless heap_fragmentation > max_heap_fragmentation
return { threshold_violated: false, payload: {} } if heap_fragmentation <= max_heap_fragmentation
{ threshold_violated: true, payload: payload(heap_fragmentation) }
end

View File

@ -0,0 +1,35 @@
# frozen_string_literal: true
module Gitlab
module Memory
class Watchdog
module Monitor
class RssMemoryLimit
attr_reader :memory_limit
def initialize(memory_limit:)
@memory_limit = memory_limit
end
def call
worker_rss = Gitlab::Metrics::System.memory_usage_rss[:total]
return { threshold_violated: false, payload: {} } if worker_rss <= memory_limit
{ threshold_violated: true, payload: payload(worker_rss, memory_limit) }
end
private
def payload(worker_rss, memory_limit)
{
message: 'rss memory limit exceeded',
memwd_rss_bytes: worker_rss,
memwd_max_rss_bytes: memory_limit
}
end
end
end
end
end
end

View File

@ -16,7 +16,7 @@ module Gitlab
reference_uss = reference_mem[:uss]
memory_limit = max_mem_growth * reference_uss
return { threshold_violated: false, payload: {} } unless worker_uss > memory_limit
return { threshold_violated: false, payload: {} } if worker_uss <= memory_limit
{ threshold_violated: true, payload: payload(worker_uss, reference_uss, memory_limit) }
end

View File

@ -7787,9 +7787,6 @@ msgstr ""
msgid "Certificate Subject"
msgstr ""
msgid "Change"
msgstr ""
msgid "Change Failure Rate"
msgstr ""
@ -12008,9 +12005,6 @@ msgstr ""
msgid "DORA4Metrics|%{startDate} - %{endDate}"
msgstr ""
msgid "DORA4Metrics|30 days before that"
msgstr ""
msgid "DORA4Metrics|Average (last %{days}d)"
msgstr ""
@ -12035,9 +12029,6 @@ msgstr ""
msgid "DORA4Metrics|Deployment frequency"
msgstr ""
msgid "DORA4Metrics|Last 30 days"
msgstr ""
msgid "DORA4Metrics|Lead time for changes"
msgstr ""
@ -12053,6 +12044,9 @@ msgstr ""
msgid "DORA4Metrics|Median time an incident was open in a production environment over the given time period."
msgstr ""
msgid "DORA4Metrics|Month to date"
msgstr ""
msgid "DORA4Metrics|No incidents during this period"
msgstr ""
@ -48511,9 +48505,6 @@ msgstr ""
msgid "mrWidget|%{boldHeaderStart}Looks like there's no pipeline here.%{boldHeaderEnd}"
msgstr ""
msgid "mrWidget|%{linkStart}Set up now%{linkEnd} to analyze your source code for known security vulnerabilities."
msgstr ""
msgid "mrWidget|%{mergeError}."
msgstr ""
@ -48758,9 +48749,6 @@ msgstr ""
msgid "mrWidget|Revoke approval"
msgstr ""
msgid "mrWidget|SAST and Secret Detection is not enabled."
msgstr ""
msgid "mrWidget|Set by %{merge_author} to be added to the merge train when the pipeline succeeds"
msgstr ""

View File

@ -75,6 +75,7 @@ end
def delete_hook
FileUtils.rm(HOOK_PATH)
system("git checkout master")
puts "#{SHELL_YELLOW}Security harness removed -- you can now push to all remotes.#{SHELL_CLEAR}"
end

View File

@ -35,7 +35,7 @@ RSpec.describe Pajamas::SpinnerComponent, type: :component do
describe 'inline' do
context 'by default' do
it 'renders a div' do
expect(page).to have_css 'div.gl-spinner'
expect(page).to have_css 'div.gl-spinner-container'
end
end
@ -43,7 +43,7 @@ RSpec.describe Pajamas::SpinnerComponent, type: :component do
let(:options) { { inline: true } }
it 'renders a span' do
expect(page).to have_css 'span.gl-spinner'
expect(page).to have_css 'span.gl-spinner-container'
end
end
end

View File

@ -9,14 +9,28 @@ module Pajamas
# @param label text
# @param size select [[small, sm], [medium, md], [large, lg], [extra large, xl]]
def default(inline: false, label: "Loading", size: :md)
render(Pajamas::SpinnerComponent.new(inline: inline, label: label, size: size))
render Pajamas::SpinnerComponent.new(
inline: inline,
label: label,
size: size
)
end
# Use a light spinner on dark backgrounds
# Use a light spinner on dark backgrounds.
#
# @display bg_color "#222"
def light
render(Pajamas::SpinnerComponent.new(color: :light))
end
# Any extra HTML attributes like `class`, `data` or `id` get automatically applied to the spinner container element.
#
def extra_attributes
render Pajamas::SpinnerComponent.new(
class: "js-do-something",
data: { foo: "bar" },
id: "my-special-spinner"
)
end
end
end

View File

@ -1,4 +1,9 @@
import { getDateWithUTC, newDateAsLocaleTime } from '~/lib/utils/datetime/date_calculation_utility';
import {
getDateWithUTC,
newDateAsLocaleTime,
nSecondsAfter,
nSecondsBefore,
} from '~/lib/utils/datetime/date_calculation_utility';
describe('newDateAsLocaleTime', () => {
it.each`
@ -31,3 +36,33 @@ describe('getDateWithUTC', () => {
expect(getDateWithUTC(date)).toEqual(expected);
});
});
describe('nSecondsAfter', () => {
const start = new Date('2022-03-22T01:23:45.678Z');
it.each`
date | seconds | expected
${start} | ${0} | ${start}
${start} | ${1} | ${new Date('2022-03-22T01:23:46.678Z')}
${start} | ${5} | ${new Date('2022-03-22T01:23:50.678Z')}
${start} | ${60} | ${new Date('2022-03-22T01:24:45.678Z')}
${start} | ${3600} | ${new Date('2022-03-22T02:23:45.678Z')}
${start} | ${86400} | ${new Date('2022-03-23T01:23:45.678Z')}
`('returns $expected given $string', ({ date, seconds, expected }) => {
expect(nSecondsAfter(date, seconds)).toEqual(expected);
});
});
describe('nSecondsBefore', () => {
const start = new Date('2022-03-22T01:23:45.678Z');
it.each`
date | seconds | expected
${start} | ${0} | ${start}
${start} | ${1} | ${new Date('2022-03-22T01:23:44.678Z')}
${start} | ${5} | ${new Date('2022-03-22T01:23:40.678Z')}
${start} | ${60} | ${new Date('2022-03-22T01:22:45.678Z')}
${start} | ${3600} | ${new Date('2022-03-22T00:23:45.678Z')}
${start} | ${86400} | ${new Date('2022-03-21T01:23:45.678Z')}
`('returns $expected given $string', ({ date, seconds, expected }) => {
expect(nSecondsBefore(date, seconds)).toEqual(expected);
});
});

View File

@ -234,7 +234,7 @@ RSpec.describe IconsHelper do
describe 'gl_loading_icon' do
it 'returns the default spinner markup' do
expect(gl_loading_icon.to_s)
.to eq '<div class="gl-spinner-container" role="status"><span class="gl-spinner gl-spinner-dark gl-spinner-sm gl-vertical-align-text-bottom!" aria-label="Loading"></span></div>'
.to eq '<div class="gl-spinner-container" role="status"><span aria-label="Loading" class="gl-spinner gl-spinner-sm gl-spinner-dark gl-vertical-align-text-bottom!"></span></div>'
end
context 'when css_class is provided' do

View File

@ -29,28 +29,10 @@ RSpec.describe 'memory watchdog' do
run_initializer
end
shared_examples 'starts configured watchdog' do |handler_class|
let(:configuration) { Gitlab::Memory::Watchdog::Configuration.new }
let(:watchdog_monitors_params) do
{
Gitlab::Memory::Watchdog::Monitor::HeapFragmentation => {
max_heap_fragmentation: max_heap_fragmentation,
max_strikes: max_strikes
},
Gitlab::Memory::Watchdog::Monitor::UniqueMemoryGrowth => {
max_mem_growth: max_mem_growth,
max_strikes: max_strikes
}
}
end
shared_examples 'starts configured watchdog' do |configure_monitor_method|
shared_examples 'configures and starts watchdog' do
it "correctly configures and starts watchdog", :aggregate_failures do
expect(watchdog).to receive(:configure).and_yield(configuration)
watchdog_monitors_params.each do |monitor_class, params|
expect(configuration.monitors).to receive(:use).with(monitor_class, **params)
end
expect(Gitlab::Memory::Watchdog::Configurator).to receive(configure_monitor_method)
expect(Gitlab::Memory::Watchdog).to receive(:new).and_return(watchdog)
expect(Gitlab::BackgroundTask).to receive(:new).with(watchdog).and_return(background_task)
@ -58,71 +40,24 @@ RSpec.describe 'memory watchdog' do
expect(Gitlab::Cluster::LifecycleEvents).to receive(:on_worker_start).and_yield
run_initializer
expect(configuration.handler).to be_an_instance_of(handler_class)
expect(configuration.logger).to eq(logger)
expect(configuration.sleep_time_seconds).to eq(sleep_time_seconds)
end
end
context 'when settings are not passed through the environment' do
let(:max_strikes) { 5 }
let(:max_heap_fragmentation) { 0.5 }
let(:max_mem_growth) { 3.0 }
let(:sleep_time_seconds) { 60 }
include_examples 'configures and starts watchdog'
end
context 'when settings are passed through the environment' do
let(:max_strikes) { 6 }
let(:max_heap_fragmentation) { 0.4 }
let(:max_mem_growth) { 2.0 }
let(:sleep_time_seconds) { 50 }
before do
stub_env('GITLAB_MEMWD_MAX_STRIKES', 6)
stub_env('GITLAB_MEMWD_SLEEP_TIME_SEC', 50)
stub_env('GITLAB_MEMWD_MAX_MEM_GROWTH', 2.0)
stub_env('GITLAB_MEMWD_MAX_HEAP_FRAG', 0.4)
end
include_examples 'configures and starts watchdog'
end
end
# In tests, the Puma constant does not exist so we cannot use a verified double.
# rubocop: disable RSpec/VerifiedDoubles
context 'when puma' do
let(:puma) do
Class.new do
def self.cli_config
Struct.new(:options).new
end
end
end
before do
stub_const('Puma', puma)
stub_const('Puma::Cluster::WorkerHandle', double.as_null_object)
allow(Gitlab::Runtime).to receive(:puma?).and_return(true)
end
it_behaves_like 'starts configured watchdog', Gitlab::Memory::Watchdog::PumaHandler
it_behaves_like 'starts configured watchdog', :configure_for_puma
end
# rubocop: enable RSpec/VerifiedDoubles
context 'when sidekiq' do
before do
allow(Gitlab::Runtime).to receive(:sidekiq?).and_return(true)
end
it_behaves_like 'starts configured watchdog', Gitlab::Memory::Watchdog::TermProcessHandler
end
context 'when other runtime' do
it_behaves_like 'starts configured watchdog', Gitlab::Memory::Watchdog::NullHandler
it_behaves_like 'starts configured watchdog', :configure_for_sidekiq
end
end

View File

@ -0,0 +1,43 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Rails YAML safe load' do
let(:unsafe_load) { false }
let(:klass) do
Class.new(ActiveRecord::Base) do
self.table_name = 'issues'
serialize :description
end
end
let(:instance) { klass.new(description: data) }
context 'with default permitted classes' do
let(:data) do
{
'time' => Time.now,
'date' => Date.today,
'number' => 1,
'hashie-array' => Hashie::Array.new([1, 2]),
'array' => [5, 6]
}
end
it 'deserializes data' do
instance.save!
expect(klass.find(instance.id).description).to eq(data)
end
context 'with unpermitted classes' do
let(:data) { { 'test' => create(:user) } }
it 'throws an exception' do
expect { instance.save! }.to raise_error(Psych::DisallowedClass)
end
end
end
end

View File

@ -4,11 +4,47 @@ require 'spec_helper'
RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
describe '#satisfied_by?' do
shared_examples 'an exists rule with a context' do
subject(:satisfied_by?) { described_class.new(globs).satisfied_by?(nil, context) }
shared_examples 'a rules:exists with a context' do
it_behaves_like 'a glob matching rule' do
let(:project) { create(:project, :custom_repo, files: files) }
end
context 'when the rules:exists has a variable' do
let_it_be(:project) { create(:project, :custom_repo, files: { 'helm/helm_file.txt' => '' }) }
let(:globs) { ['$HELM_DIR/**/*'] }
let(:variables_hash) do
{ 'HELM_DIR' => 'helm' }
end
before do
allow(context).to receive(:variables_hash).and_return(variables_hash)
end
context 'when the ci_variables_rules_exists FF is disabled' do
before do
stub_feature_flags(ci_variable_expansion_in_rules_exists: false)
end
it { is_expected.to be_falsey }
end
context 'when the ci_variables_rules_exists FF is enabled' do
context 'when the context has the specified variables' do
it { is_expected.to be_truthy }
end
context 'when variable expansion does not match' do
let(:variables_hash) { {} }
it { is_expected.to be_falsey }
end
end
end
context 'after pattern comparision limit is reached' do
let(:globs) { ['*definitely_not_a_matching_glob*'] }
let(:project) { create(:project, :repository) }
@ -22,26 +58,24 @@ RSpec.describe Gitlab::Ci::Build::Rules::Rule::Clause::Exists do
end
end
subject(:satisfied_by?) { described_class.new(globs).satisfied_by?(nil, context) }
context 'when context is Build::Context::Build' do
it_behaves_like 'an exists rule with a context' do
context 'when the rules are being evaluated at job level' do
it_behaves_like 'a rules:exists with a context' do
let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.commit.sha) }
let(:context) { Gitlab::Ci::Build::Context::Build.new(pipeline, sha: project.repository.commit.sha) }
end
end
context 'when context is Build::Context::Global' do
it_behaves_like 'an exists rule with a context' do
context 'when the rules are being evaluated for an entire pipeline' do
it_behaves_like 'a rules:exists with a context' do
let(:pipeline) { build(:ci_pipeline, project: project, sha: project.repository.commit.sha) }
let(:context) { Gitlab::Ci::Build::Context::Global.new(pipeline, yaml_variables: {}) }
end
end
context 'when context is Config::External::Context' do
context 'when rules are being evaluated with `include`' do
let(:context) { Gitlab::Ci::Config::External::Context.new(project: project, sha: sha) }
it_behaves_like 'an exists rule with a context' do
it_behaves_like 'a rules:exists with a context' do
let(:sha) { project.repository.commit.sha }
end

View File

@ -78,36 +78,53 @@ RSpec.describe Gitlab::Memory::Watchdog::Configuration do
end
end
context 'when two monitors are configured to be used' do
before do
configuration.monitors.use monitor_class_1, false, { message: 'monitor_1_text' }, max_strikes: 5
configuration.monitors.use monitor_class_2, true, { message: 'monitor_2_text' }, max_strikes: 0
context 'when two different monitor class are configured' do
shared_examples 'executes monitors and returns correct results' do
it 'calls each monitor and returns correct results', :aggregate_failures do
payloads = []
thresholds = []
strikes = []
monitor_names = []
configuration.monitors.call_each do |result|
payloads << result.payload
thresholds << result.threshold_violated?
strikes << result.strikes_exceeded?
monitor_names << result.monitor_name
end
expect(payloads).to eq([payload1, payload2])
expect(thresholds).to eq([false, true])
expect(strikes).to eq([false, true])
expect(monitor_names).to eq([:monitor1, :monitor2])
end
end
it 'calls each monitor and returns correct results', :aggregate_failures do
payloads = []
thresholds = []
strikes = []
monitor_names = []
configuration.monitors.call_each do |result|
payloads << result.payload
thresholds << result.threshold_violated?
strikes << result.strikes_exceeded?
monitor_names << result.monitor_name
context 'when monitors are configured inline' do
before do
configuration.monitors.push monitor_class_1, false, { message: 'monitor_1_text' }, max_strikes: 5
configuration.monitors.push monitor_class_2, true, { message: 'monitor_2_text' }, max_strikes: 0
end
expect(payloads).to eq([payload1, payload2])
expect(thresholds).to eq([false, true])
expect(strikes).to eq([false, true])
expect(monitor_names).to eq([:monitor1, :monitor2])
include_examples 'executes monitors and returns correct results'
end
context 'when monitors are configured in a block' do
before do
configuration.monitors do |stack|
stack.push monitor_class_1, false, { message: 'monitor_1_text' }, max_strikes: 5
stack.push monitor_class_2, true, { message: 'monitor_2_text' }, max_strikes: 0
end
end
include_examples 'executes monitors and returns correct results'
end
end
context 'when same monitor class is configured to be used twice' do
context 'when same monitor class is configured twice' do
before do
configuration.monitors.use monitor_class_1, max_strikes: 1
configuration.monitors.use monitor_class_1, max_strikes: 1
configuration.monitors.push monitor_class_1, max_strikes: 1
configuration.monitors.push monitor_class_1, max_strikes: 1
end
it 'calls same monitor only once' do

View File

@ -0,0 +1,187 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'prometheus/client'
require_dependency 'gitlab/cluster/lifecycle_events'
RSpec.describe Gitlab::Memory::Watchdog::Configurator do
shared_examples 'as configurator' do |handler_class, sleep_time_env, sleep_time|
it 'configures the correct handler' do
configurator.call(configuration)
expect(configuration.handler).to be_an_instance_of(handler_class)
end
it 'configures the correct logger' do
configurator.call(configuration)
expect(configuration.logger).to eq(logger)
end
context 'when sleep_time_seconds is not passed through the environment' do
let(:sleep_time_seconds) { sleep_time }
it 'configures the correct sleep time' do
configurator.call(configuration)
expect(configuration.sleep_time_seconds).to eq(sleep_time_seconds)
end
end
context 'when sleep_time_seconds is passed through the environment' do
let(:sleep_time_seconds) { sleep_time - 1 }
before do
stub_env(sleep_time_env, sleep_time - 1)
end
it 'configures the correct sleep time' do
configurator.call(configuration)
expect(configuration.sleep_time_seconds).to eq(sleep_time_seconds)
end
end
end
shared_examples 'as monitor configurator' do
it 'executes monitors and returns correct results' do
configurator.call(configuration)
payloads = {}
configuration.monitors.call_each do |result|
payloads[result.monitor_name] = result.payload
end
expect(payloads).to eq(expected_payloads)
end
end
let(:configuration) { Gitlab::Memory::Watchdog::Configuration.new }
# In tests, the Puma constant does not exist so we cannot use a verified double.
# rubocop: disable RSpec/VerifiedDoubles
describe '.configure_for_puma' do
let(:logger) { Gitlab::AppLogger }
let(:puma) do
Class.new do
def self.cli_config
Struct.new(:options).new
end
end
end
subject(:configurator) { described_class.configure_for_puma }
def stub_prometheus_metrics
gauge = instance_double(::Prometheus::Client::Gauge)
allow(Gitlab::Metrics).to receive(:gauge).and_return(gauge)
allow(gauge).to receive(:set)
end
before do
stub_const('Puma', puma)
stub_const('Puma::Cluster::WorkerHandle', double.as_null_object)
stub_prometheus_metrics
end
it_behaves_like 'as configurator',
Gitlab::Memory::Watchdog::PumaHandler,
'GITLAB_MEMWD_SLEEP_TIME_SEC',
60
context 'with DISABLE_PUMA_WORKER_KILLER set to true' do
let(:primary_memory) { 2048 }
let(:worker_memory) { max_mem_growth * primary_memory + 1 }
let(:expected_payloads) do
{
heap_fragmentation: {
message: 'heap fragmentation limit exceeded',
memwd_cur_heap_frag: max_heap_fragmentation + 0.1,
memwd_max_heap_frag: max_heap_fragmentation,
memwd_max_strikes: max_strikes,
memwd_cur_strikes: 1
},
unique_memory_growth: {
message: 'memory limit exceeded',
memwd_uss_bytes: worker_memory,
memwd_ref_uss_bytes: primary_memory,
memwd_max_uss_bytes: max_mem_growth * primary_memory,
memwd_max_strikes: max_strikes,
memwd_cur_strikes: 1
}
}
end
before do
stub_env('DISABLE_PUMA_WORKER_KILLER', true)
allow(Gitlab::Metrics::Memory).to receive(:gc_heap_fragmentation).and_return(max_heap_fragmentation + 0.1)
allow(Gitlab::Metrics::System).to receive(:memory_usage_uss_pss).and_return({ uss: worker_memory })
allow(Gitlab::Metrics::System).to receive(:memory_usage_uss_pss).with(
pid: Gitlab::Cluster::PRIMARY_PID
).and_return({ uss: primary_memory })
end
context 'when settings are set via environment variables' do
let(:max_heap_fragmentation) { 0.4 }
let(:max_mem_growth) { 4.0 }
let(:max_strikes) { 4 }
before do
stub_env('GITLAB_MEMWD_MAX_HEAP_FRAG', 0.4)
stub_env('GITLAB_MEMWD_MAX_MEM_GROWTH', 4.0)
stub_env('GITLAB_MEMWD_MAX_STRIKES', 4)
end
it_behaves_like 'as monitor configurator'
end
context 'when settings are not set via environment variables' do
let(:max_heap_fragmentation) { 0.5 }
let(:max_mem_growth) { 3.0 }
let(:max_strikes) { 5 }
it_behaves_like 'as monitor configurator'
end
end
context 'with DISABLE_PUMA_WORKER_KILLER set to false' do
let(:expected_payloads) do
{
rss_memory_limit: {
message: 'rss memory limit exceeded',
memwd_rss_bytes: memory_limit + 1,
memwd_max_rss_bytes: memory_limit,
memwd_max_strikes: max_strikes,
memwd_cur_strikes: 1
}
}
end
before do
stub_env('DISABLE_PUMA_WORKER_KILLER', false)
allow(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return({ total: memory_limit + 1 })
end
context 'when settings are set via environment variables' do
let(:memory_limit) { 1300 }
let(:max_strikes) { 4 }
before do
stub_env('PUMA_WORKER_MAX_MEMORY', 1300)
stub_env('GITLAB_MEMWD_MAX_STRIKES', 4)
end
it_behaves_like 'as monitor configurator'
end
context 'when settings are not set via environment variables' do
let(:memory_limit) { 1200 }
let(:max_strikes) { 5 }
it_behaves_like 'as monitor configurator'
end
end
end
# rubocop: enable RSpec/VerifiedDoubles
end

View File

@ -0,0 +1,39 @@
# frozen_string_literal: true
require 'fast_spec_helper'
require 'support/shared_examples/lib/gitlab/memory/watchdog/monitor_result_shared_examples'
RSpec.describe Gitlab::Memory::Watchdog::Monitor::RssMemoryLimit do
let(:memory_limit) { 2048 }
let(:worker_memory) { 1024 }
subject(:monitor) do
described_class.new(memory_limit: memory_limit)
end
before do
allow(Gitlab::Metrics::System).to receive(:memory_usage_rss).and_return({ total: worker_memory })
end
describe '#call' do
context 'when process exceeds threshold' do
let(:worker_memory) { memory_limit + 1 }
let(:payload) do
{
message: 'rss memory limit exceeded',
memwd_rss_bytes: worker_memory,
memwd_max_rss_bytes: memory_limit
}
end
include_examples 'returns Watchdog Monitor result', threshold_violated: true
end
context 'when process does not exceed threshold' do
let(:worker_memory) { memory_limit - 1 }
let(:payload) { {} }
include_examples 'returns Watchdog Monitor result', threshold_violated: false
end
end
end

View File

@ -69,7 +69,7 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures do
config.handler = handler
config.logger = logger
config.sleep_time_seconds = sleep_time_seconds
config.monitors.use monitor_class, threshold_violated, payload, max_strikes: max_strikes
config.monitors.push monitor_class, threshold_violated, payload, max_strikes: max_strikes
end
allow(handler).to receive(:call).and_return(true)
@ -205,8 +205,8 @@ RSpec.describe Gitlab::Memory::Watchdog, :aggregate_failures do
config.handler = handler
config.logger = logger
config.sleep_time_seconds = sleep_time_seconds
config.monitors.use monitor_class, threshold_violated, payload, max_strikes: max_strikes
config.monitors.use monitor_class, threshold_violated, payload, max_strikes: max_strikes
config.monitors.push monitor_class, threshold_violated, payload, max_strikes: max_strikes
config.monitors.push monitor_class, threshold_violated, payload, max_strikes: max_strikes
end
end

View File

@ -16,6 +16,7 @@ RSpec.describe Emails::Releases do
subject { Notify.new_release_email(user.id, release) }
it_behaves_like 'an email sent from GitLab'
it_behaves_like 'an email with X-GitLab headers containing project details'
context 'when the release has a name' do
it 'shows the correct subject' do