Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
c0a3d287c0
commit
e87220d9c1
|
@ -1,10 +1,7 @@
|
|||
---
|
||||
Rails/FilePath:
|
||||
# Offense count: 212
|
||||
# Temporarily disabled due to too many offenses
|
||||
Enabled: false
|
||||
Details: grace period
|
||||
Exclude:
|
||||
- 'app/controllers/clusters/clusters_controller.rb'
|
||||
- 'app/controllers/help_controller.rb'
|
||||
- 'app/helpers/startupjs_helper.rb'
|
||||
- 'app/models/clusters/applications/cert_manager.rb'
|
||||
|
@ -29,6 +26,7 @@ Rails/FilePath:
|
|||
- 'ee/db/fixtures/development/21_dast_profiles.rb'
|
||||
- 'ee/db/fixtures/development/32_compliance_report_violations.rb'
|
||||
- 'ee/lib/ee/feature/definition.rb'
|
||||
- 'ee/lib/ee/gitlab/audit/type/definition.rb'
|
||||
- 'ee/lib/ee/gitlab/usage/metric_definition.rb'
|
||||
- 'ee/lib/gitlab/geo/health_check.rb'
|
||||
- 'ee/lib/tasks/gitlab/seed/metrics.rake'
|
||||
|
@ -48,6 +46,8 @@ Rails/FilePath:
|
|||
- 'lib/api/api.rb'
|
||||
- 'lib/error_tracking/collector/payload_validator.rb'
|
||||
- 'lib/feature/definition.rb'
|
||||
- 'lib/gitlab/audit/type/definition.rb'
|
||||
- 'lib/gitlab/ci/parsers/sbom/validators/cyclonedx_schema_validator.rb'
|
||||
- 'lib/gitlab/ci/reports/codequality_reports.rb'
|
||||
- 'lib/gitlab/database/migrations/runner.rb'
|
||||
- 'lib/gitlab/favicon.rb'
|
||||
|
@ -69,14 +69,17 @@ Rails/FilePath:
|
|||
- 'lib/system_check/app/systemd_unit_files_or_init_script_up_to_date_check.rb'
|
||||
- 'lib/system_check/app/uploads_directory_exists_check.rb'
|
||||
- 'lib/system_check/incoming_email/imap_authentication_check.rb'
|
||||
- 'lib/tasks/gitlab/db.rake'
|
||||
- 'lib/tasks/gitlab/metrics_exporter.rake'
|
||||
- 'lib/tasks/gitlab/usage_data.rake'
|
||||
- 'lib/tasks/tanuki_emoji.rake'
|
||||
- 'metrics_server/metrics_server.rb'
|
||||
- 'spec/commands/metrics_server/metrics_server_spec.rb'
|
||||
- 'spec/config/object_store_settings_spec.rb'
|
||||
- 'spec/controllers/help_controller_spec.rb'
|
||||
- 'spec/db/development/add_security_training_providers_spec.rb'
|
||||
- 'spec/db/development/create_base_work_item_types_spec.rb'
|
||||
- 'spec/db/development/import_common_metrics_spec.rb'
|
||||
- 'spec/db/production/add_security_training_providers_spec.rb'
|
||||
- 'spec/db/production/create_base_work_item_types_spec.rb'
|
||||
- 'spec/db/production/import_common_metrics_spec.rb'
|
||||
- 'spec/db/schema_spec.rb'
|
||||
|
@ -100,7 +103,6 @@ Rails/FilePath:
|
|||
- 'spec/features/projects/settings/repository_settings_spec.rb'
|
||||
- 'spec/features/projects/settings/user_changes_avatar_spec.rb'
|
||||
- 'spec/features/projects/snippets/create_snippet_spec.rb'
|
||||
- 'spec/features/projects/tags/user_edits_tags_spec.rb'
|
||||
- 'spec/features/projects/tree/upload_file_spec.rb'
|
||||
- 'spec/features/snippets/user_creates_snippet_spec.rb'
|
||||
- 'spec/features/snippets/user_edits_snippet_spec.rb'
|
||||
|
@ -127,10 +129,12 @@ Rails/FilePath:
|
|||
- 'spec/models/clusters/applications/cert_manager_spec.rb'
|
||||
- 'spec/models/release_highlight_spec.rb'
|
||||
- 'spec/requests/api/internal/mail_room_spec.rb'
|
||||
- 'spec/requests/api/usage_data_queries_spec.rb'
|
||||
- 'spec/serializers/review_app_setup_entity_spec.rb'
|
||||
- 'spec/services/clusters/aws/fetch_credentials_service_spec.rb'
|
||||
- 'spec/services/clusters/aws/provision_service_spec.rb'
|
||||
- 'spec/services/metrics/sample_metrics_service_spec.rb'
|
||||
- 'spec/support/helpers/doc_url_helper.rb'
|
||||
- 'spec/support/helpers/test_env.rb'
|
||||
- 'spec/support/helpers/upload_helpers.rb'
|
||||
- 'spec/support/shared_examples/features/project_upload_files_shared_examples.rb'
|
||||
|
@ -139,3 +143,4 @@ Rails/FilePath:
|
|||
- 'spec/support/shared_examples/models/wiki_shared_examples.rb'
|
||||
- 'spec/tasks/gitlab/db_rake_spec.rb'
|
||||
- 'spec/tasks/gitlab/generate_sample_prometheus_data_spec.rb'
|
||||
- 'spec/tasks/gitlab/usage_data_rake_spec.rb'
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
---
|
||||
# Cop supports --auto-correct.
|
||||
Style/CaseLikeIf:
|
||||
# Offense count: 60
|
||||
# Temporarily disabled due to too many offenses
|
||||
Enabled: false
|
||||
Details: grace period
|
||||
Exclude:
|
||||
- 'app/controllers/concerns/issuable_actions.rb'
|
||||
- 'app/controllers/groups/dependency_proxy/application_controller.rb'
|
||||
|
@ -13,6 +11,7 @@ Style/CaseLikeIf:
|
|||
- 'app/helpers/broadcast_messages_helper.rb'
|
||||
- 'app/helpers/issues_helper.rb'
|
||||
- 'app/helpers/routing/pseudonymization_helper.rb'
|
||||
- 'app/helpers/todos_helper.rb'
|
||||
- 'app/models/integrations/jira.rb'
|
||||
- 'app/models/members/member_task.rb'
|
||||
- 'app/models/namespace.rb'
|
||||
|
@ -21,8 +20,11 @@ Style/CaseLikeIf:
|
|||
- 'app/services/google_cloud/generate_pipeline_service.rb'
|
||||
- 'app/services/issuable/bulk_update_service.rb'
|
||||
- 'app/services/todo_service.rb'
|
||||
- 'app/services/user_project_access_changed_service.rb'
|
||||
- 'ee/app/controllers/concerns/credentials_inventory_actions.rb'
|
||||
- 'ee/app/finders/ee/notes_finder.rb'
|
||||
- 'ee/app/finders/security/scan_execution_policies_finder.rb'
|
||||
- 'ee/app/finders/security/training_providers/secure_code_warrior_url_finder.rb'
|
||||
- 'ee/app/helpers/ee/branches_helper.rb'
|
||||
- 'ee/app/services/epics/tree_reorder_service.rb'
|
||||
- 'ee/app/services/merge_request_approval_settings/update_service.rb'
|
||||
|
@ -43,18 +45,18 @@ Style/CaseLikeIf:
|
|||
- 'lib/gitlab/utils/strong_memoize.rb'
|
||||
- 'qa/qa/git/repository.rb'
|
||||
- 'qa/qa/scenario/bootable.rb'
|
||||
- 'rubocop/cop/gitlab/keys_first_and_values_first.rb'
|
||||
- 'spec/features/boards/user_adds_lists_to_board_spec.rb'
|
||||
- 'spec/lib/gitlab/auth/auth_finders_spec.rb'
|
||||
- 'spec/lib/gitlab/database/load_balancing_spec.rb'
|
||||
- 'spec/lib/omni_auth/strategies/jwt_spec.rb'
|
||||
- 'spec/models/concerns/sha_attribute_spec.rb'
|
||||
- 'spec/models/preloaders/labels_preloader_spec.rb'
|
||||
- 'spec/requests/api/personal_access_tokens_spec.rb'
|
||||
- 'spec/requests/api/rubygem_packages_spec.rb'
|
||||
- 'spec/requests/api/terraform/modules/v1/packages_spec.rb'
|
||||
- 'spec/services/ci/pipeline_processing/atomic_processing_service_spec.rb'
|
||||
- 'spec/services/resource_events/change_state_service_spec.rb'
|
||||
- 'spec/support/helpers/filter_spec_helper.rb'
|
||||
- 'spec/support/matchers/abort_matcher.rb'
|
||||
- 'spec/support/shared_examples/metrics/active_record_subscriber_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/requests/api/notes_shared_examples.rb'
|
||||
- 'spec/support/shared_examples/uploaders/object_storage_shared_examples.rb'
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
---
|
||||
# Cop supports --auto-correct.
|
||||
Style/EmptyMethod:
|
||||
# Offense count: 240
|
||||
# Temporarily disabled due to too many offenses
|
||||
Enabled: false
|
||||
Details: grace period
|
||||
Exclude:
|
||||
- 'app/controllers/admin/application_settings/appearances_controller.rb'
|
||||
- 'app/controllers/admin/applications_controller.rb'
|
||||
- 'app/controllers/admin/broadcast_messages_controller.rb'
|
||||
- 'app/controllers/admin/deploy_keys_controller.rb'
|
||||
- 'app/controllers/admin/hook_logs_controller.rb'
|
||||
- 'app/controllers/admin/hooks_controller.rb'
|
||||
- 'app/controllers/admin/identities_controller.rb'
|
||||
- 'app/controllers/admin/labels_controller.rb'
|
||||
- 'app/controllers/admin/runners_controller.rb'
|
||||
|
@ -35,13 +31,11 @@ Style/EmptyMethod:
|
|||
- 'app/controllers/projects/alert_management_controller.rb'
|
||||
- 'app/controllers/projects/ci/lints_controller.rb'
|
||||
- 'app/controllers/projects/ci/pipeline_editor_controller.rb'
|
||||
- 'app/controllers/projects/ci/secure_files_controller.rb'
|
||||
- 'app/controllers/projects/confluences_controller.rb'
|
||||
- 'app/controllers/projects/deploy_keys_controller.rb'
|
||||
- 'app/controllers/projects/environments_controller.rb'
|
||||
- 'app/controllers/projects/feature_flags_controller.rb'
|
||||
- 'app/controllers/projects/feature_flags_user_lists_controller.rb'
|
||||
- 'app/controllers/projects/hook_logs_controller.rb'
|
||||
- 'app/controllers/projects/import/jira_controller.rb'
|
||||
- 'app/controllers/projects/imports_controller.rb'
|
||||
- 'app/controllers/projects/incidents_controller.rb'
|
||||
|
@ -55,30 +49,32 @@ Style/EmptyMethod:
|
|||
- 'app/controllers/projects/runners_controller.rb'
|
||||
- 'app/controllers/projects/settings/integrations_controller.rb'
|
||||
- 'app/controllers/projects/settings/packages_and_registries_controller.rb'
|
||||
- 'app/controllers/projects/tags/releases_controller.rb'
|
||||
- 'app/controllers/projects/terraform_controller.rb'
|
||||
- 'app/controllers/projects/triggers_controller.rb'
|
||||
- 'app/controllers/pwa_controller.rb'
|
||||
- 'app/controllers/registrations/welcome_controller.rb'
|
||||
- 'app/controllers/search_controller.rb'
|
||||
- 'app/experiments/security_actions_continuous_onboarding_experiment.rb'
|
||||
- 'app/graphql/resolvers/concerns/caching_array_resolver.rb'
|
||||
- 'app/helpers/subscribable_banner_helper.rb'
|
||||
- 'app/helpers/users/callouts_helper.rb'
|
||||
- 'app/models/ci/bridge.rb'
|
||||
- 'app/models/ci/job_artifact.rb'
|
||||
- 'app/models/concerns/cross_database_modification.rb'
|
||||
- 'app/models/concerns/reactive_caching.rb'
|
||||
- 'app/models/concerns/relative_positioning.rb'
|
||||
- 'app/models/hooks/web_hook.rb'
|
||||
- 'app/models/integrations/hangouts_chat.rb'
|
||||
- 'app/models/integrations/microsoft_teams.rb'
|
||||
- 'app/models/integrations/pumble.rb'
|
||||
- 'app/models/integrations/unify_circuit.rb'
|
||||
- 'app/models/integrations/webex_teams.rb'
|
||||
- 'app/models/wiki.rb'
|
||||
- 'app/services/auto_merge/base_service.rb'
|
||||
- 'app/services/award_emojis/destroy_service.rb'
|
||||
- 'app/services/groups/transfer_service.rb'
|
||||
- 'app/services/issuable_base_service.rb'
|
||||
- 'app/services/issues/reopen_service.rb'
|
||||
- 'app/services/projects/transfer_service.rb'
|
||||
- 'app/workers/authorized_projects_worker.rb'
|
||||
- 'app/workers/namespaces/root_statistics_worker.rb'
|
||||
- 'db/migrate/20210420012444_change_web_hook_events_default.rb'
|
||||
- 'db/migrate/20210507191949_add_remove_on_issue_close_to_labels.rb'
|
||||
|
@ -92,6 +88,7 @@ Style/EmptyMethod:
|
|||
- 'db/post_migrate/20220324032250_migrate_shimo_confluence_service_category.rb'
|
||||
- 'db/post_migrate/20220412143552_consume_remaining_encrypt_integration_property_jobs.rb'
|
||||
- 'db/post_migrate/20220425121435_backfill_integrations_enable_ssl_verification.rb'
|
||||
- 'db/post_migrate/20220524074947_finalize_backfill_null_note_discussion_ids.rb'
|
||||
- 'ee/app/controllers/admin/emails_controller.rb'
|
||||
- 'ee/app/controllers/admin/geo/designs_controller.rb'
|
||||
- 'ee/app/controllers/admin/geo/settings_controller.rb'
|
||||
|
@ -101,9 +98,8 @@ Style/EmptyMethod:
|
|||
- 'ee/app/controllers/groups/analytics/devops_adoption_controller.rb'
|
||||
- 'ee/app/controllers/groups/compliance_frameworks_controller.rb'
|
||||
- 'ee/app/controllers/groups/feature_discovery_moments_controller.rb'
|
||||
- 'ee/app/controllers/groups/hooks_controller.rb'
|
||||
- 'ee/app/controllers/groups/ldap_group_links_controller.rb'
|
||||
- 'ee/app/controllers/groups/push_rules_controller.rb'
|
||||
- 'ee/app/controllers/groups/settings/reporting_controller.rb'
|
||||
- 'ee/app/controllers/projects/analytics/code_reviews_controller.rb'
|
||||
- 'ee/app/controllers/projects/analytics/merge_request_analytics_controller.rb'
|
||||
- 'ee/app/controllers/projects/incident_management/escalation_policies_controller.rb'
|
||||
|
@ -122,8 +118,10 @@ Style/EmptyMethod:
|
|||
- 'ee/app/controllers/subscriptions/groups_controller.rb'
|
||||
- 'ee/app/controllers/trial_registrations_controller.rb'
|
||||
- 'ee/app/controllers/trials_controller.rb'
|
||||
- 'ee/app/controllers/users/identity_verification_controller.rb'
|
||||
- 'ee/app/experiments/cart_abandonment_modal_experiment.rb'
|
||||
- 'ee/app/models/ee/epic.rb'
|
||||
- 'ee/app/models/geo/group_wiki_repository_registry.rb'
|
||||
- 'ee/app/services/feature_flag_issues/destroy_service.rb'
|
||||
- 'ee/db/geo/migrate/20170906174622_remove_duplicates_from_project_registry.rb'
|
||||
- 'lib/api/helpers/packages/conan/api_helpers.rb'
|
||||
|
@ -135,6 +133,8 @@ Style/EmptyMethod:
|
|||
- 'lib/gitlab/alert_management/payload/base.rb'
|
||||
- 'lib/gitlab/background_migration/backfill_iteration_cadence_id_for_boards.rb'
|
||||
- 'lib/gitlab/background_migration/create_security_setting.rb'
|
||||
- 'lib/gitlab/background_migration/delete_approval_rules_with_vulnerability.rb'
|
||||
- 'lib/gitlab/background_migration/delete_invalid_epic_issues.rb'
|
||||
- 'lib/gitlab/background_migration/drop_invalid_remediations.rb'
|
||||
- 'lib/gitlab/background_migration/fix_incorrect_max_seats_used.rb'
|
||||
- 'lib/gitlab/background_migration/migrate_approver_to_approval_rules.rb'
|
||||
|
@ -142,6 +142,7 @@ Style/EmptyMethod:
|
|||
- 'lib/gitlab/background_migration/migrate_approver_to_approval_rules_in_batch.rb'
|
||||
- 'lib/gitlab/background_migration/migrate_job_artifact_registry_to_ssf.rb'
|
||||
- 'lib/gitlab/background_migration/migrate_requirements_to_work_items.rb'
|
||||
- 'lib/gitlab/background_migration/migrate_shared_vulnerability_scanners.rb'
|
||||
- 'lib/gitlab/background_migration/recalculate_vulnerability_finding_signatures_for_findings.rb'
|
||||
- 'lib/gitlab/background_migration/update_vulnerability_occurrences_location.rb'
|
||||
- 'lib/gitlab/ci/config/entry/need.rb'
|
||||
|
@ -150,10 +151,10 @@ Style/EmptyMethod:
|
|||
- 'lib/gitlab/ci/pipeline/chain/validate/after_config.rb'
|
||||
- 'lib/gitlab/config/entry/node.rb'
|
||||
- 'lib/gitlab/config/entry/simplifiable.rb'
|
||||
- 'lib/gitlab/email/message/in_product_marketing/experience.rb'
|
||||
- 'lib/gitlab/empty_search_results.rb'
|
||||
- 'lib/gitlab/git_access.rb'
|
||||
- 'lib/gitlab/import_export/json/ndjson_writer.rb'
|
||||
- 'lib/gitlab/mailgun/webhook_processors/base.rb'
|
||||
- 'lib/gitlab/null_request_store.rb'
|
||||
- 'lib/gitlab/usage_data_non_sql_metrics.rb'
|
||||
- 'lib/mattermost/session.rb'
|
||||
|
@ -162,7 +163,6 @@ Style/EmptyMethod:
|
|||
- 'qa/qa/resource/job.rb'
|
||||
- 'qa/qa/resource/package.rb'
|
||||
- 'qa/qa/resource/registry_repository.rb'
|
||||
- 'qa/qa/resource/runner.rb'
|
||||
- 'qa/qa/service/cluster_provider/k3d.rb'
|
||||
- 'qa/qa/service/cluster_provider/k3s.rb'
|
||||
- 'qa/qa/service/cluster_provider/minikube.rb'
|
||||
|
@ -173,7 +173,6 @@ Style/EmptyMethod:
|
|||
- 'spec/lib/gitlab/database/load_balancing/sidekiq_client_middleware_spec.rb'
|
||||
- 'spec/lib/gitlab/database/load_balancing/sidekiq_server_middleware_spec.rb'
|
||||
- 'spec/lib/gitlab/database/migration_helpers/restrict_gitlab_schema_spec.rb'
|
||||
- 'spec/lib/gitlab/database/migrations/background_migration_helpers_spec.rb'
|
||||
- 'spec/lib/gitlab/database/partitioning/sliding_list_strategy_spec.rb'
|
||||
- 'spec/lib/gitlab/database/postgresql_adapter/dump_schema_versions_mixin_spec.rb'
|
||||
- 'spec/lib/gitlab/database/postgresql_database_tasks/load_schema_versions_mixin_spec.rb'
|
||||
|
@ -192,4 +191,5 @@ Style/EmptyMethod:
|
|||
- 'spec/lib/gitlab/utils/delegator_override/validator_spec.rb'
|
||||
- 'spec/lib/gitlab/utils/delegator_override_spec.rb'
|
||||
- 'spec/lib/gitlab/utils/override_spec.rb'
|
||||
- 'spec/lib/gitlab/utils/strong_memoize_spec.rb'
|
||||
- 'spec/workers/concerns/waitable_worker_spec.rb'
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { setUrlParams } from '~/lib/utils/url_utility';
|
||||
import { createAlert } from '~/flash';
|
||||
import { __ } from '~/locale';
|
||||
|
||||
export default function redirectToCorrectBlamePage() {
|
||||
const { hash } = window.location;
|
||||
const linesPerPage = parseInt(document.querySelector('.js-per-page').dataset.perPage, 10);
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const currentPage = parseInt(params.get('page'), 10);
|
||||
const isPaginationDisabled = params.get('no_pagination');
|
||||
if (hash && linesPerPage && !isPaginationDisabled) {
|
||||
const lineNumber = parseInt(hash.split('#L')[1], 10);
|
||||
const pageToRedirect = Math.ceil(lineNumber / linesPerPage);
|
||||
const isRedirectNeeded =
|
||||
(pageToRedirect > 1 && pageToRedirect !== currentPage) || pageToRedirect < currentPage;
|
||||
if (isRedirectNeeded) {
|
||||
createAlert({
|
||||
message: __('Please wait a few moments while we load the file history for this line.'),
|
||||
});
|
||||
window.location.href = setUrlParams({ page: pageToRedirect });
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "https://gitlab.com/.gitlab-ci.yml",
|
||||
"title": "Gitlab CI configuration",
|
||||
"markdownDescription": "Gitlab has a built-in solution for doing CI called Gitlab CI. It is configured by supplying a file called `.gitlab-ci.yml`, which will list all the jobs that are going to run for the project. A full list of all options can be found [here](https://docs.gitlab.com/ee/ci/yaml). [Learn More](https://docs.gitlab.com/ee/ci/index.html).",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
@ -408,6 +407,13 @@
|
|||
}
|
||||
},
|
||||
"required": ["name"]
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"minLength": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"markdownDescription": "Specifies the docker image to use for the job or globally for all jobs. Job configuration takes precedence over global setting. Requires a certain kind of Gitlab runner executor. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#image)."
|
||||
|
@ -1047,10 +1053,21 @@
|
|||
]
|
||||
},
|
||||
"stage": {
|
||||
"type": "string",
|
||||
"description": "Define what stage the job will run in.",
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"minLength": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"only": {
|
||||
"$ref": "#/definitions/filter",
|
||||
"description": "Job will run *only* when these filtering options match."
|
||||
|
@ -1583,10 +1600,23 @@
|
|||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"minLength": 1,
|
||||
"markdownDescription": "Used to select runners from the list of available runners. A runner must have all tags listed here to run the job. [Learn More](https://docs.gitlab.com/ee/ci/yaml/#tags).",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"minLength": 1,
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import initBlob from '~/pages/projects/init_blob';
|
||||
import redirectToCorrectPage from '~/blame/blame_redirect';
|
||||
|
||||
redirectToCorrectPage();
|
||||
initBlob();
|
||||
|
|
|
@ -56,7 +56,12 @@ export default {
|
|||
|
||||
<template>
|
||||
<gl-tabs>
|
||||
<gl-tab ref="pipelineTab" :title="$options.i18n.tabs.pipelineTitle" data-testid="pipeline-tab">
|
||||
<gl-tab
|
||||
ref="pipelineTab"
|
||||
:title="$options.i18n.tabs.pipelineTitle"
|
||||
data-testid="pipeline-tab"
|
||||
lazy
|
||||
>
|
||||
<pipeline-graph-wrapper />
|
||||
</gl-tab>
|
||||
<gl-tab
|
||||
|
@ -64,6 +69,7 @@ export default {
|
|||
:title="$options.i18n.tabs.needsTitle"
|
||||
:active="isActive($options.tabNames.needs)"
|
||||
data-testid="dag-tab"
|
||||
lazy
|
||||
>
|
||||
<dag />
|
||||
</gl-tab>
|
||||
|
|
|
@ -26,7 +26,10 @@ class Projects::BlameController < Projects::ApplicationController
|
|||
blame_service = Projects::BlameService.new(@blob, @commit, params.permit(:page, :no_pagination))
|
||||
|
||||
@blame = Gitlab::View::Presenter::Factory.new(blame_service.blame, project: @project, path: @path, page: blame_service.page).fabricate!
|
||||
|
||||
@blame_pagination = blame_service.pagination
|
||||
|
||||
@blame_per_page = blame_service.per_page
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -114,7 +114,7 @@ class RegistrationsController < Devise::RegistrationsController
|
|||
def after_sign_up_path_for(user)
|
||||
Gitlab::AppLogger.info(user_created_message(confirmed: user.confirmed?))
|
||||
|
||||
users_sign_up_welcome_path(request.query_parameters.slice(:glm_source, :glm_content))
|
||||
users_sign_up_welcome_path
|
||||
end
|
||||
|
||||
def after_inactive_sign_up_path_for(resource)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Resolvers
|
||||
class ProjectPipelineSchedulesResolver < BaseResolver
|
||||
alias_method :project, :object
|
||||
|
||||
type ::Types::Ci::PipelineScheduleType.connection_type, null: true
|
||||
|
||||
argument :status, ::Types::Ci::PipelineScheduleStatusEnum,
|
||||
required: false,
|
||||
description: 'Filter pipeline schedules by active status.'
|
||||
|
||||
def resolve(status: nil)
|
||||
::Ci::PipelineSchedulesFinder.new(project).execute(scope: status)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -148,17 +148,7 @@ module Types
|
|||
end
|
||||
|
||||
def stage
|
||||
::Gitlab::Graphql::Lazy.with_value(pipeline) do |pl|
|
||||
BatchLoader::GraphQL.for([pl, object.stage]).batch do |ids, loader|
|
||||
by_pipeline = ids
|
||||
.group_by(&:first)
|
||||
.transform_values { |grp| grp.map(&:second) }
|
||||
|
||||
by_pipeline.each do |p, names|
|
||||
p.stages.by_name(names).each { |s| loader.call([p, s.name], s) }
|
||||
end
|
||||
end
|
||||
end
|
||||
::Gitlab::Graphql::Loaders::BatchModelLoader.new(::Ci::Stage, object.stage_id).find
|
||||
end
|
||||
|
||||
# This class is a secret union!
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Ci
|
||||
class PipelineScheduleStatusEnum < BaseEnum
|
||||
graphql_name 'PipelineScheduleStatus'
|
||||
|
||||
value 'ACTIVE', value: "active", description: 'Active pipeline schedules.'
|
||||
value 'INACTIVE', value: "inactive", description: 'Inactive pipeline schedules.'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,45 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module Ci
|
||||
class PipelineScheduleType < BaseObject
|
||||
graphql_name 'PipelineSchedule'
|
||||
|
||||
connection_type_class(Types::CountableConnectionType)
|
||||
|
||||
expose_permissions Types::PermissionTypes::Ci::PipelineSchedules
|
||||
|
||||
authorize :read_pipeline_schedule
|
||||
|
||||
field :id, GraphQL::Types::ID, null: false, description: 'ID of the pipeline schedule.'
|
||||
|
||||
field :description, GraphQL::Types::String, null: true, description: 'Description of the pipeline schedule.'
|
||||
|
||||
field :owner, ::Types::UserType, null: false, description: 'Owner of the pipeline schedule.'
|
||||
|
||||
field :active, GraphQL::Types::Boolean, null: false, description: 'Indicates if a pipeline schedule is active.'
|
||||
|
||||
field :next_run_at, Types::TimeType, null: false, description: 'Time when the next pipeline will run.'
|
||||
|
||||
field :real_next_run, Types::TimeType, null: false, description: 'Time when the next pipeline will run.'
|
||||
|
||||
field :last_pipeline, PipelineType, null: true, description: 'Last pipeline object.'
|
||||
|
||||
field :ref_for_display, GraphQL::Types::String,
|
||||
null: true, description: 'Git ref for the pipeline schedule.', method: :ref_for_display
|
||||
|
||||
field :ref_path, GraphQL::Types::String, null: true, description: 'Path to the ref that triggered the pipeline.'
|
||||
|
||||
field :for_tag, GraphQL::Types::Boolean,
|
||||
null: false, description: 'Indicates if a pipelines schedule belongs to a tag.', method: :for_tag?
|
||||
|
||||
field :cron, GraphQL::Types::String, null: false, description: 'Cron notation for the schedule.'
|
||||
|
||||
field :cron_timezone, GraphQL::Types::String, null: false, description: 'Timezone for the pipeline schedule.'
|
||||
|
||||
def ref_path
|
||||
::Gitlab::Routing.url_helpers.project_commits_path(object.project, object.ref_for_display)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Types
|
||||
module PermissionTypes
|
||||
module Ci
|
||||
class PipelineSchedules < BasePermissionType
|
||||
graphql_name 'PipelineSchedulePermissions'
|
||||
|
||||
abilities :take_ownership_pipeline_schedule,
|
||||
:update_pipeline_schedule,
|
||||
:admin_pipeline_schedule
|
||||
|
||||
ability_field :play_pipeline_schedule, calls_gitaly: true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -309,6 +309,12 @@ module Types
|
|||
extras: [:lookahead],
|
||||
resolver: Resolvers::ProjectPipelinesResolver
|
||||
|
||||
field :pipeline_schedules,
|
||||
type: Types::Ci::PipelineScheduleType.connection_type,
|
||||
null: true,
|
||||
description: 'Pipeline schedules of the project. This field can only be resolved for one project per request.',
|
||||
resolver: Resolvers::ProjectPipelineSchedulesResolver
|
||||
|
||||
field :pipeline, Types::Ci::PipelineType,
|
||||
null: true,
|
||||
description: 'Build pipeline of the project.',
|
||||
|
|
|
@ -48,22 +48,19 @@ class Repository
|
|||
# For example, for entry `:commit_count` there's a method called `commit_count` which
|
||||
# stores its data in the `commit_count` cache key.
|
||||
CACHED_METHODS = %i(size commit_count readme_path contribution_guide
|
||||
changelog license_blob license_key gitignore
|
||||
changelog license_blob license_licensee license_gitaly gitignore
|
||||
gitlab_ci_yml branch_names tag_names branch_count
|
||||
tag_count avatar exists? root_ref merged_branch_names
|
||||
has_visible_content? issue_template_names_hash merge_request_template_names_hash
|
||||
user_defined_metrics_dashboard_paths xcode_project? has_ambiguous_refs?).freeze
|
||||
|
||||
# Methods that use cache_method but only memoize the value
|
||||
MEMOIZED_CACHED_METHODS = %i(license).freeze
|
||||
|
||||
# Certain method caches should be refreshed when certain types of files are
|
||||
# changed. This Hash maps file types (as returned by Gitlab::FileDetector) to
|
||||
# the corresponding methods to call for refreshing caches.
|
||||
METHOD_CACHES_FOR_FILE_TYPES = {
|
||||
readme: %i(readme_path),
|
||||
changelog: :changelog,
|
||||
license: %i(license_blob license_key license),
|
||||
license: %i(license_blob license_licensee license_gitaly),
|
||||
contributing: :contribution_guide,
|
||||
gitignore: :gitignore,
|
||||
gitlab_ci: :gitlab_ci_yml,
|
||||
|
@ -650,25 +647,30 @@ class Repository
|
|||
cache_method :license_blob
|
||||
|
||||
def license_key
|
||||
return unless exists?
|
||||
|
||||
raw_repository.license_short_name
|
||||
license&.key
|
||||
end
|
||||
cache_method :license_key
|
||||
|
||||
def license
|
||||
return unless license_key
|
||||
|
||||
licensee_object = Licensee::License.new(license_key)
|
||||
|
||||
return if licensee_object.name.blank?
|
||||
|
||||
licensee_object
|
||||
rescue Licensee::InvalidLicense => e
|
||||
Gitlab::ErrorTracking.track_exception(e)
|
||||
nil
|
||||
if Feature.enabled?(:license_from_gitaly)
|
||||
license_gitaly
|
||||
else
|
||||
license_licensee
|
||||
end
|
||||
memoize_method :license
|
||||
end
|
||||
|
||||
def license_licensee
|
||||
return unless exists?
|
||||
|
||||
raw_repository.license(false)
|
||||
end
|
||||
cache_method :license_licensee
|
||||
|
||||
def license_gitaly
|
||||
return unless exists?
|
||||
|
||||
raw_repository.license(true)
|
||||
end
|
||||
cache_method :license_gitaly
|
||||
|
||||
def gitignore
|
||||
file_on_head(:gitignore)
|
||||
|
|
|
@ -27,6 +27,10 @@ module Projects
|
|||
.page(page)
|
||||
end
|
||||
|
||||
def per_page
|
||||
PER_PAGE
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :blob, :commit, :pagination_enabled
|
||||
|
@ -48,10 +52,6 @@ module Projects
|
|||
page
|
||||
end
|
||||
|
||||
def per_page
|
||||
PER_PAGE
|
||||
end
|
||||
|
||||
def pagination_state(params)
|
||||
return false if Gitlab::Utils.to_boolean(params[:no_pagination], default: false)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
.signup-page
|
||||
= render 'devise/shared/signup_box',
|
||||
url: registration_path(resource_name, request.query_parameters.slice(:glm_source, :glm_content)),
|
||||
url: registration_path(resource_name),
|
||||
button_text: _('Register'),
|
||||
borderless: Feature.enabled?(:restyle_login_page, @project),
|
||||
show_omniauth_providers: omniauth_enabled? && button_based_providers_enabled?
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
- page_title _("Blame"), @blob.path, @ref
|
||||
- add_page_specific_style 'page_bundles/tree'
|
||||
|
||||
#blob-content-holder.tree-holder{ data: { testid: 'blob-content-holder' } }
|
||||
#blob-content-holder.tree-holder.js-per-page{ data: { testid: 'blob-content-holder', per_page: @blame_per_page } }
|
||||
= render "projects/blob/breadcrumb", blob: @blob, blame: true
|
||||
|
||||
.file-holder.gl-overflow-hidden
|
||||
|
|
|
@ -17,9 +17,7 @@
|
|||
%p.gl-text-center= html_escape(_('%{gitlab_experience_text}. We won\'t share this information with anyone.')) % { gitlab_experience_text: gitlab_experience_text }
|
||||
- else
|
||||
%p.gl-text-center= html_escape(_('%{gitlab_experience_text}. Don\'t worry, this information isn\'t shared outside of your self-managed GitLab instance.')) % { gitlab_experience_text: gitlab_experience_text }
|
||||
= gitlab_ui_form_for(current_user,
|
||||
url: users_sign_up_welcome_path(request.query_parameters.slice(:glm_source, :glm_content)),
|
||||
html: { class: 'card gl-w-full! gl-p-5 js-users-signup-welcome', 'aria-live' => 'assertive' }) do |f|
|
||||
= gitlab_ui_form_for(current_user, url: users_sign_up_welcome_path, html: { class: 'card gl-w-full! gl-p-5 js-users-signup-welcome', 'aria-live' => 'assertive' }) do |f|
|
||||
.devise-errors
|
||||
= render 'devise/shared/error_messages', resource: current_user
|
||||
.row
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
- advanced_deployments
|
||||
- advisory_database
|
||||
- api_security
|
||||
- application_instrumentation
|
||||
- application_performance
|
||||
- attack_emulation
|
||||
- audit_events
|
||||
|
@ -38,6 +39,7 @@
|
|||
- dedicated
|
||||
- delivery
|
||||
- dependency_firewall
|
||||
- dependency_management
|
||||
- dependency_proxy
|
||||
- dependency_scanning
|
||||
- deployment_management
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
---
|
||||
name: bypass_batch_pop_queueing_for_merge_trains
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96793
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/372366
|
||||
milestone: '15.4'
|
||||
name: license_from_gitaly
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/77041
|
||||
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/374300
|
||||
milestone: '15.5'
|
||||
type: development
|
||||
group: group::scalability
|
||||
group: group::gitaly
|
||||
default_enabled: false
|
|
@ -3,9 +3,7 @@ table_name: sbom_component_versions
|
|||
classes:
|
||||
- Sbom::ComponentVersion
|
||||
feature_categories:
|
||||
- container_scanning
|
||||
- dependency_scanning
|
||||
- license_compliance
|
||||
- dependency_management
|
||||
description: Stores version information for software components produced by a Software Bill of Materials (SBoM)
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90809
|
||||
milestone: '15.2'
|
||||
|
|
|
@ -3,9 +3,7 @@ table_name: sbom_components
|
|||
classes:
|
||||
- Sbom::Component
|
||||
feature_categories:
|
||||
- container_scanning
|
||||
- dependency_scanning
|
||||
- license_compliance
|
||||
- dependency_management
|
||||
description: Stores information about software components produced by a Software Bill of Materials (SBoM)
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90809
|
||||
milestone: '15.2'
|
||||
|
|
|
@ -3,9 +3,7 @@ table_name: sbom_occurrences
|
|||
classes:
|
||||
- Sbom::Occurrence
|
||||
feature_categories:
|
||||
- container_scanning
|
||||
- dependency_scanning
|
||||
- license_compliance
|
||||
- dependency_management
|
||||
description: Tracks each occurrence of an SBoM component
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90814
|
||||
milestone: '15.2'
|
||||
|
|
|
@ -3,9 +3,7 @@ table_name: sbom_sources
|
|||
classes:
|
||||
- Sbom::Source
|
||||
feature_categories:
|
||||
- container_scanning
|
||||
- dependency_scanning
|
||||
- license_compliance
|
||||
- dependency_management
|
||||
description: Stores information about where an SBoM component originated from
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/90812
|
||||
milestone: '15.2'
|
||||
|
|
|
@ -3,9 +3,7 @@ table_name: sbom_vulnerable_component_versions
|
|||
classes:
|
||||
- Sbom::VulnerableComponentVersion
|
||||
feature_categories:
|
||||
- container_scanning
|
||||
- dependency_scanning
|
||||
- license_compliance
|
||||
- dependency_management
|
||||
description: Stores information about vulnerable SBoM components
|
||||
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95622
|
||||
milestone: '15.4'
|
||||
|
|
|
@ -5,15 +5,12 @@ class FinalizeInvalidGroupMemberCleanup < Gitlab::Database::Migration[2.0]
|
|||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
MIGRATION = 'DestroyInvalidGroupMembers'
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: MIGRATION,
|
||||
table_name: :members,
|
||||
column_name: :id,
|
||||
job_arguments: []
|
||||
)
|
||||
# noop: this fails because the cleanup invalid members migration(ScheduleDestroyInvalidGroupMembers)
|
||||
# cannot succeed, so we need to cleanup that first.
|
||||
#
|
||||
# issue with some details: https://gitlab.com/gitlab-org/gitlab/-/issues/365028#note_1107166816
|
||||
# # incident: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/7779
|
||||
end
|
||||
|
||||
def down
|
||||
|
|
|
@ -5,15 +5,12 @@ class FinalizeInvalidProjectMemberCleanup < Gitlab::Database::Migration[2.0]
|
|||
|
||||
restrict_gitlab_migration gitlab_schema: :gitlab_main
|
||||
|
||||
MIGRATION = 'DestroyInvalidProjectMembers'
|
||||
|
||||
def up
|
||||
ensure_batched_background_migration_is_finished(
|
||||
job_class_name: MIGRATION,
|
||||
table_name: :members,
|
||||
column_name: :id,
|
||||
job_arguments: []
|
||||
)
|
||||
# noop: this fails because the cleanup invalid members migration(ScheduleDestroyInvalidProjectMembers)
|
||||
# cannot succeed, so we need to cleanup that first.
|
||||
#
|
||||
# issue with some details: https://gitlab.com/gitlab-org/gitlab/-/issues/365028#note_1107166816
|
||||
# # incident: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/7779
|
||||
end
|
||||
|
||||
def down
|
||||
|
|
|
@ -8352,6 +8352,30 @@ The edge type for [`Pipeline`](#pipeline).
|
|||
| <a id="pipelineedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="pipelineedgenode"></a>`node` | [`Pipeline`](#pipeline) | The item at the end of the edge. |
|
||||
|
||||
#### `PipelineScheduleConnection`
|
||||
|
||||
The connection type for [`PipelineSchedule`](#pipelineschedule).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="pipelinescheduleconnectioncount"></a>`count` | [`Int!`](#int) | Total count of collection. |
|
||||
| <a id="pipelinescheduleconnectionedges"></a>`edges` | [`[PipelineScheduleEdge]`](#pipelinescheduleedge) | A list of edges. |
|
||||
| <a id="pipelinescheduleconnectionnodes"></a>`nodes` | [`[PipelineSchedule]`](#pipelineschedule) | A list of nodes. |
|
||||
| <a id="pipelinescheduleconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. |
|
||||
|
||||
#### `PipelineScheduleEdge`
|
||||
|
||||
The edge type for [`PipelineSchedule`](#pipelineschedule).
|
||||
|
||||
##### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="pipelinescheduleedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. |
|
||||
| <a id="pipelinescheduleedgenode"></a>`node` | [`PipelineSchedule`](#pipelineschedule) | The item at the end of the edge. |
|
||||
|
||||
#### `PipelineSecurityReportFindingConnection`
|
||||
|
||||
The connection type for [`PipelineSecurityReportFinding`](#pipelinesecurityreportfinding).
|
||||
|
@ -15892,6 +15916,37 @@ Represents pipeline counts for the project.
|
|||
| <a id="pipelinepermissionsdestroypipeline"></a>`destroyPipeline` | [`Boolean!`](#boolean) | Indicates the user can perform `destroy_pipeline` on this resource. |
|
||||
| <a id="pipelinepermissionsupdatepipeline"></a>`updatePipeline` | [`Boolean!`](#boolean) | Indicates the user can perform `update_pipeline` on this resource. |
|
||||
|
||||
### `PipelineSchedule`
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="pipelinescheduleactive"></a>`active` | [`Boolean!`](#boolean) | Indicates if a pipeline schedule is active. |
|
||||
| <a id="pipelineschedulecron"></a>`cron` | [`String!`](#string) | Cron notation for the schedule. |
|
||||
| <a id="pipelineschedulecrontimezone"></a>`cronTimezone` | [`String!`](#string) | Timezone for the pipeline schedule. |
|
||||
| <a id="pipelinescheduledescription"></a>`description` | [`String`](#string) | Description of the pipeline schedule. |
|
||||
| <a id="pipelineschedulefortag"></a>`forTag` | [`Boolean!`](#boolean) | Indicates if a pipelines schedule belongs to a tag. |
|
||||
| <a id="pipelinescheduleid"></a>`id` | [`ID!`](#id) | ID of the pipeline schedule. |
|
||||
| <a id="pipelineschedulelastpipeline"></a>`lastPipeline` | [`Pipeline`](#pipeline) | Last pipeline object. |
|
||||
| <a id="pipelineschedulenextrunat"></a>`nextRunAt` | [`Time!`](#time) | Time when the next pipeline will run. |
|
||||
| <a id="pipelinescheduleowner"></a>`owner` | [`UserCore!`](#usercore) | Owner of the pipeline schedule. |
|
||||
| <a id="pipelineschedulerealnextrun"></a>`realNextRun` | [`Time!`](#time) | Time when the next pipeline will run. |
|
||||
| <a id="pipelineschedulereffordisplay"></a>`refForDisplay` | [`String`](#string) | Git ref for the pipeline schedule. |
|
||||
| <a id="pipelineschedulerefpath"></a>`refPath` | [`String`](#string) | Path to the ref that triggered the pipeline. |
|
||||
| <a id="pipelinescheduleuserpermissions"></a>`userPermissions` | [`PipelineSchedulePermissions!`](#pipelineschedulepermissions) | Permissions for the current user on the resource. |
|
||||
|
||||
### `PipelineSchedulePermissions`
|
||||
|
||||
#### Fields
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="pipelineschedulepermissionsadminpipelineschedule"></a>`adminPipelineSchedule` | [`Boolean!`](#boolean) | Indicates the user can perform `admin_pipeline_schedule` on this resource. |
|
||||
| <a id="pipelineschedulepermissionsplaypipelineschedule"></a>`playPipelineSchedule` | [`Boolean!`](#boolean) | Indicates the user can perform `play_pipeline_schedule` on this resource. |
|
||||
| <a id="pipelineschedulepermissionstakeownershippipelineschedule"></a>`takeOwnershipPipelineSchedule` | [`Boolean!`](#boolean) | Indicates the user can perform `take_ownership_pipeline_schedule` on this resource. |
|
||||
| <a id="pipelineschedulepermissionsupdatepipelineschedule"></a>`updatePipelineSchedule` | [`Boolean!`](#boolean) | Indicates the user can perform `update_pipeline_schedule` on this resource. |
|
||||
|
||||
### `PipelineSecurityReportFinding`
|
||||
|
||||
Represents vulnerability finding of a security report on the pipeline.
|
||||
|
@ -16774,6 +16829,22 @@ Returns [`PipelineCounts`](#pipelinecounts).
|
|||
| <a id="projectpipelinecountssha"></a>`sha` | [`String`](#string) | Filter pipelines by the SHA of the commit they are run for. |
|
||||
| <a id="projectpipelinecountssource"></a>`source` | [`String`](#string) | Filter pipelines by their source. |
|
||||
|
||||
##### `Project.pipelineSchedules`
|
||||
|
||||
Pipeline schedules of the project. This field can only be resolved for one project per request.
|
||||
|
||||
Returns [`PipelineScheduleConnection`](#pipelinescheduleconnection).
|
||||
|
||||
This field returns a [connection](#connections). It accepts the
|
||||
four standard [pagination arguments](#connection-pagination-arguments):
|
||||
`before: String`, `after: String`, `first: Int`, `last: Int`.
|
||||
|
||||
###### Arguments
|
||||
|
||||
| Name | Type | Description |
|
||||
| ---- | ---- | ----------- |
|
||||
| <a id="projectpipelineschedulesstatus"></a>`status` | [`PipelineScheduleStatus`](#pipelineschedulestatus) | Filter pipeline schedules by active status. |
|
||||
|
||||
##### `Project.pipelines`
|
||||
|
||||
Build pipelines of the project.
|
||||
|
@ -20942,6 +21013,13 @@ Event type of the pipeline associated with a merge request.
|
|||
| <a id="pipelinemergerequesteventtypemerged_result"></a>`MERGED_RESULT` | Pipeline run on the changes from the source branch combined with the target branch. |
|
||||
| <a id="pipelinemergerequesteventtypemerge_train"></a>`MERGE_TRAIN` | Pipeline ran as part of a merge train. |
|
||||
|
||||
### `PipelineScheduleStatus`
|
||||
|
||||
| Value | Description |
|
||||
| ----- | ----------- |
|
||||
| <a id="pipelineschedulestatusactive"></a>`ACTIVE` | Active pipeline schedules. |
|
||||
| <a id="pipelineschedulestatusinactive"></a>`INACTIVE` | Inactive pipeline schedules. |
|
||||
|
||||
### `PipelineScopeEnum`
|
||||
|
||||
| Value | Description |
|
||||
|
|
|
@ -76,6 +76,9 @@ Example of response
|
|||
"failure_reason": "script_failure",
|
||||
"tag": false,
|
||||
"web_url": "https://example.com/foo/bar/-/jobs/7",
|
||||
"project": {
|
||||
"ci_job_token_scope_enabled": false
|
||||
},
|
||||
"user": {
|
||||
"id": 1,
|
||||
"name": "Administrator",
|
||||
|
@ -132,6 +135,9 @@ Example of response
|
|||
"failure_reason": "stuck_or_timeout_failure",
|
||||
"tag": false,
|
||||
"web_url": "https://example.com/foo/bar/-/jobs/6",
|
||||
"project": {
|
||||
"ci_job_token_scope_enabled": false
|
||||
},
|
||||
"user": {
|
||||
"id": 1,
|
||||
"name": "Administrator",
|
||||
|
@ -216,6 +222,9 @@ Example of response
|
|||
"failure_reason": "stuck_or_timeout_failure",
|
||||
"tag": false,
|
||||
"web_url": "https://example.com/foo/bar/-/jobs/6",
|
||||
"project": {
|
||||
"ci_job_token_scope_enabled": false
|
||||
},
|
||||
"user": {
|
||||
"id": 1,
|
||||
"name": "Administrator",
|
||||
|
@ -281,6 +290,9 @@ Example of response
|
|||
"failure_reason": "script_failure",
|
||||
"tag": false,
|
||||
"web_url": "https://example.com/foo/bar/-/jobs/7",
|
||||
"project": {
|
||||
"ci_job_token_scope_enabled": false
|
||||
},
|
||||
"user": {
|
||||
"id": 1,
|
||||
"name": "Administrator",
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
redirect_to: 'downstream_pipelines.md'
|
||||
remove_date: '2022-11-31'
|
||||
remove_date: '2022-11-30'
|
||||
---
|
||||
|
||||
This document was moved to [another location](downstream_pipelines.md).
|
||||
|
||||
<!-- This redirect file can be deleted after <2022-11-31>. -->
|
||||
<!-- This redirect file can be deleted after <2022-11-30>. -->
|
||||
<!-- Redirects that point to other docs in the same project expire in three months. -->
|
||||
<!-- Redirects that point to docs in a different project or site (link is not relative and starts with `https:`) expire in one year. -->
|
||||
<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/redirects.html -->
|
||||
|
|
|
@ -68,9 +68,9 @@ We should ensure that:
|
|||
|
||||
To provide markup with accessible names, ensure every:
|
||||
|
||||
- `input` has an associated `label`.
|
||||
- `button` and `a` have child text, or `aria-label` when child text isn't present, such as for an icon button with no content.
|
||||
- `img` has an `alt` attribute.
|
||||
- input has an [associated `label`](#examples-of-providing-accessible-names).
|
||||
- button and link have [visible text](#buttons-and-links-with-descriptive-accessible-names), or `aria-label` when there is no visible text, such as for an icon button with no content.
|
||||
- image has an [`alt` attribute](#images-with-accessible-names).
|
||||
- `fieldset` has `legend` as its first child.
|
||||
- `figure` has `figcaption` as its first child.
|
||||
- `table` has `caption` as its first child.
|
||||
|
@ -100,12 +100,12 @@ Text input examples:
|
|||
```html
|
||||
<!-- Input with label -->
|
||||
<gl-form-group :label="__('Issue title')" label-for="issue-title">
|
||||
<gl-form-input id="issue-title" v-model="title" name="title" />
|
||||
<gl-form-input id="issue-title" v-model="title" />
|
||||
</gl-form-group>
|
||||
|
||||
<!-- Input with hidden label -->
|
||||
<gl-form-group :label="__('Issue title')" label-for="issue-title" :label-sr-only="true">
|
||||
<gl-form-input id="issue-title" v-model="title" name="title" />
|
||||
<gl-form-group :label="__('Issue title')" label-for="issue-title" label-sr-only>
|
||||
<gl-form-input id="issue-title" v-model="title" />
|
||||
</gl-form-group>
|
||||
```
|
||||
|
||||
|
@ -114,12 +114,12 @@ Textarea examples:
|
|||
```html
|
||||
<!-- Textarea with label -->
|
||||
<gl-form-group :label="__('Issue description')" label-for="issue-description">
|
||||
<gl-form-textarea id="issue-description" v-model="description" name="description" />
|
||||
<gl-form-textarea id="issue-description" v-model="description" />
|
||||
</gl-form-group>
|
||||
|
||||
<!-- Textarea with hidden label -->
|
||||
<gl-form-group :label="__('Issue description')" label-for="issue-description" :label-sr-only="true">
|
||||
<gl-form-textarea id="issue-description" v-model="description" name="description" />
|
||||
<gl-form-group :label="__('Issue description')" label-for="issue-description" label-sr-only>
|
||||
<gl-form-textarea id="issue-description" v-model="description" />
|
||||
</gl-form-group>
|
||||
```
|
||||
|
||||
|
@ -128,11 +128,11 @@ Alternatively, you can use a plain `label` element:
|
|||
```html
|
||||
<!-- Input with label using `label` -->
|
||||
<label for="issue-title">{{ __('Issue title') }}</label>
|
||||
<gl-form-input id="issue-title" v-model="title" name="title" />
|
||||
<gl-form-input id="issue-title" v-model="title" />
|
||||
|
||||
<!-- Input with hidden label using `label` -->
|
||||
<label for="issue-title" class="gl-sr-only">{{ __('Issue title') }}</label>
|
||||
<gl-form-input id="issue-title" v-model="title" name="title" />
|
||||
<gl-form-input id="issue-title" v-model="title" />
|
||||
```
|
||||
|
||||
#### Select inputs with accessible names
|
||||
|
@ -142,12 +142,12 @@ Select input examples:
|
|||
```html
|
||||
<!-- Select input with label -->
|
||||
<gl-form-group :label="__('Issue status')" label-for="issue-status">
|
||||
<gl-form-select id="issue-status" v-model="status" name="status" :options="options" />
|
||||
<gl-form-select id="issue-status" v-model="status" :options="options" />
|
||||
</gl-form-group>
|
||||
|
||||
<!-- Select input with hidden label -->
|
||||
<gl-form-group :label="__('Issue status')" label-for="issue-status" :label-sr-only="true">
|
||||
<gl-form-select id="issue-status" v-model="status" name="status" :options="options" />
|
||||
<gl-form-group :label="__('Issue status')" label-for="issue-status" label-sr-only>
|
||||
<gl-form-select id="issue-status" v-model="status" :options="options" />
|
||||
</gl-form-group>
|
||||
```
|
||||
|
||||
|
@ -157,12 +157,12 @@ Single checkbox:
|
|||
|
||||
```html
|
||||
<!-- Single checkbox with label -->
|
||||
<gl-form-checkbox v-model="status" name="status" value="task-complete">
|
||||
<gl-form-checkbox v-model="status" value="task-complete">
|
||||
{{ __('Task complete') }}
|
||||
</gl-form-checkbox>
|
||||
|
||||
<!-- Single checkbox with hidden label -->
|
||||
<gl-form-checkbox v-model="status" name="status" value="task-complete">
|
||||
<gl-form-checkbox v-model="status" value="task-complete">
|
||||
<span class="gl-sr-only">{{ __('Task complete') }}</span>
|
||||
</gl-form-checkbox>
|
||||
```
|
||||
|
@ -172,24 +172,24 @@ Multiple checkboxes:
|
|||
```html
|
||||
<!-- Multiple labeled checkboxes grouped within a fieldset -->
|
||||
<gl-form-group :label="__('Task list')">
|
||||
<gl-form-checkbox name="task-list" value="task-1">{{ __('Task 1') }}</gl-form-checkbox>
|
||||
<gl-form-checkbox name="task-list" value="task-2">{{ __('Task 2') }}</gl-form-checkbox>
|
||||
<gl-form-checkbox value="task-1">{{ __('Task 1') }}</gl-form-checkbox>
|
||||
<gl-form-checkbox value="task-2">{{ __('Task 2') }}</gl-form-checkbox>
|
||||
</gl-form-group>
|
||||
|
||||
<!-- Or -->
|
||||
<gl-form-group :label="__('Task list')">
|
||||
<gl-form-checkbox-group v-model="selected" :options="options" name="task-list" />
|
||||
<gl-form-checkbox-group v-model="selected" :options="options" />
|
||||
</gl-form-group>
|
||||
|
||||
<!-- Multiple labeled checkboxes grouped within a fieldset with hidden legend -->
|
||||
<gl-form-group :label="__('Task list')" :label-sr-only="true">
|
||||
<gl-form-checkbox name="task-list" value="task-1">{{ __('Task 1') }}</gl-form-checkbox>
|
||||
<gl-form-checkbox name="task-list" value="task-2">{{ __('Task 2') }}</gl-form-checkbox>
|
||||
<gl-form-group :label="__('Task list')" label-sr-only>
|
||||
<gl-form-checkbox value="task-1">{{ __('Task 1') }}</gl-form-checkbox>
|
||||
<gl-form-checkbox value="task-2">{{ __('Task 2') }}</gl-form-checkbox>
|
||||
</gl-form-group>
|
||||
|
||||
<!-- Or -->
|
||||
<gl-form-group :label="__('Task list')" :label-sr-only="true">
|
||||
<gl-form-checkbox-group v-model="selected" :options="options" name="task-list" />
|
||||
<gl-form-group :label="__('Task list')" label-sr-only>
|
||||
<gl-form-checkbox-group v-model="selected" :options="options" />
|
||||
</gl-form-group>
|
||||
```
|
||||
|
||||
|
@ -199,12 +199,12 @@ Single radio input:
|
|||
|
||||
```html
|
||||
<!-- Single radio with a label -->
|
||||
<gl-form-radio v-model="status" name="status" value="opened">
|
||||
<gl-form-radio v-model="status" value="opened">
|
||||
{{ __('Opened') }}
|
||||
</gl-form-radio>
|
||||
|
||||
<!-- Single radio with a hidden label -->
|
||||
<gl-form-radio v-model="status" name="status" value="opened">
|
||||
<gl-form-radio v-model="status" value="opened">
|
||||
<span class="gl-sr-only">{{ __('Opened') }}</span>
|
||||
</gl-form-radio>
|
||||
```
|
||||
|
@ -214,24 +214,24 @@ Multiple radio inputs:
|
|||
```html
|
||||
<!-- Multiple labeled radio inputs grouped within a fieldset -->
|
||||
<gl-form-group :label="__('Issue status')">
|
||||
<gl-form-radio name="status" value="opened">{{ __('Opened') }}</gl-form-radio>
|
||||
<gl-form-radio name="status" value="closed">{{ __('Closed') }}</gl-form-radio>
|
||||
<gl-form-radio value="opened">{{ __('Opened') }}</gl-form-radio>
|
||||
<gl-form-radio value="closed">{{ __('Closed') }}</gl-form-radio>
|
||||
</gl-form-group>
|
||||
|
||||
<!-- Or -->
|
||||
<gl-form-group :label="__('Issue status')">
|
||||
<gl-form-radio-group v-model="selected" :options="options" name="status" />
|
||||
<gl-form-radio-group v-model="selected" :options="options" />
|
||||
</gl-form-group>
|
||||
|
||||
<!-- Multiple labeled radio inputs grouped within a fieldset with hidden legend -->
|
||||
<gl-form-group :label="__('Issue status')" :label-sr-only="true">
|
||||
<gl-form-radio name="status" value="opened">{{ __('Opened') }}</gl-form-radio>
|
||||
<gl-form-radio name="status" value="closed">{{ __('Closed') }}</gl-form-radio>
|
||||
<gl-form-group :label="__('Issue status')" label-sr-only>
|
||||
<gl-form-radio value="opened">{{ __('Opened') }}</gl-form-radio>
|
||||
<gl-form-radio value="closed">{{ __('Closed') }}</gl-form-radio>
|
||||
</gl-form-group>
|
||||
|
||||
<!-- Or -->
|
||||
<gl-form-group :label="__('Issue status')" :label-sr-only="true">
|
||||
<gl-form-radio-group v-model="selected" :options="options" name="status" />
|
||||
<gl-form-group :label="__('Issue status')" label-sr-only>
|
||||
<gl-form-radio-group v-model="selected" :options="options" />
|
||||
</gl-form-group>
|
||||
```
|
||||
|
||||
|
@ -242,11 +242,11 @@ File input examples:
|
|||
```html
|
||||
<!-- File input with a label -->
|
||||
<label for="attach-file">{{ __('Attach a file') }}</label>
|
||||
<input id="attach-file" type="file" name="attach-file" />
|
||||
<input id="attach-file" type="file" />
|
||||
|
||||
<!-- File input with a hidden label -->
|
||||
<label for="attach-file" class="gl-sr-only">{{ __('Attach a file') }}</label>
|
||||
<input id="attach-file" type="file" name="attach-file" />
|
||||
<input id="attach-file" type="file" />
|
||||
```
|
||||
|
||||
#### GlToggle components with an accessible names
|
||||
|
@ -337,7 +337,7 @@ element is interactive you must ensure:
|
|||
- It can receive keyboard focus.
|
||||
- It has a visible focus state.
|
||||
|
||||
Use semantic HTML, such as `a` and `button`, which provides these behaviours by default.
|
||||
Use semantic HTML, such as `a` (`GlLink`) and `button` (`GlButton`), which provides these behaviours by default.
|
||||
|
||||
Keep in mind that:
|
||||
|
||||
|
@ -351,7 +351,7 @@ See the [Pajamas Keyboard-only page](https://design.gitlab.com/accessibility-aud
|
|||
|
||||
Prefer **no** `tabindex` to using `tabindex`, since:
|
||||
|
||||
- Using semantic HTML such as `button` implicitly provides `tabindex="0"`.
|
||||
- Using semantic HTML such as `button` (`GlButton`) implicitly provides `tabindex="0"`.
|
||||
- Tabbing order should match the visual reading order and positive `tabindex`s interfere with this.
|
||||
|
||||
### Avoid using `tabindex="0"` to make an element interactive
|
||||
|
@ -359,8 +359,8 @@ Prefer **no** `tabindex` to using `tabindex`, since:
|
|||
Use interactive elements instead of `div` and `span` tags.
|
||||
For example:
|
||||
|
||||
- If the element should be clickable, use a `button`.
|
||||
- If the element should be text editable, use an `input` or `textarea`.
|
||||
- If the element should be clickable, use a `button` (`GlButton`).
|
||||
- If the element should be text editable, use an [`input` or `textarea`](#text-inputs-with-accessible-names).
|
||||
|
||||
Once the markup is semantically complete, use CSS to update it to its desired visual state.
|
||||
|
||||
|
|
|
@ -213,6 +213,16 @@ To view epics in a group:
|
|||
1. On the top bar, select **Main menu > Groups** and find your group.
|
||||
1. On the left sidebar, select **Epics**.
|
||||
|
||||
### Who can view an epic
|
||||
|
||||
Whether you can view an epic depends on the [group visibility level](../../public_access.md) and
|
||||
the epic's [confidentiality status](#make-an-epic-confidential):
|
||||
|
||||
- Public group and a non-confidential epic: You don't have to be a member of the group.
|
||||
- Private group and non-confidential epic: You must have at least the Guest role for the group.
|
||||
- Confidential epic (regardless of group visibility): You must have at least the Reporter
|
||||
role for the group.
|
||||
|
||||
### Cached epic count
|
||||
|
||||
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299540) in GitLab 13.11 [with a flag](../../../administration/feature_flags.md) named `cached_sidebar_open_epics_count`. Enabled by default.
|
||||
|
@ -329,8 +339,8 @@ automatically added to the epic.
|
|||
|
||||
#### Add an existing issue to an epic
|
||||
|
||||
Existing issues that belong to a project in an epic's group, or any of the epic's
|
||||
subgroups, are eligible to be added to the epic. Newly added issues appear at the top of the list of
|
||||
You can add existing issues to an epic, including issues in a project in an epic's group, or any of
|
||||
the epic's subgroups. Newly added issues appear at the top of the list of
|
||||
issues in the **Epics and Issues** tab.
|
||||
|
||||
An epic contains a list of issues and an issue can be associated with at most one epic.
|
||||
|
@ -339,7 +349,7 @@ current parent.
|
|||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Reporter role for the epic's group.
|
||||
- You must be able to [view the epic](#who-can-view-an-epic).
|
||||
- You must be able to [edit the issue](../../project/issues/managing_issues.md#edit-an-issue).
|
||||
|
||||
To add an existing issue to an epic:
|
||||
|
@ -356,14 +366,13 @@ To add an existing issue to an epic:
|
|||
|
||||
#### Create an issue from an epic
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/5419) in GitLab 12.7.
|
||||
|
||||
Creating an issue from an epic enables you to maintain focus on the broader context of the epic
|
||||
while dividing work into smaller parts.
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- You must have at least the Reporter role for the epic's group.
|
||||
- You must be able to [view the epic](#who-can-view-an-epic).
|
||||
- You must have at least the Reporter role for the project.
|
||||
|
||||
To create an issue from an epic:
|
||||
|
||||
|
|
|
@ -91,6 +91,7 @@ The following table lists project permissions available for each role:
|
|||
| [Issue boards](project/issue_board.md):<br>Create or delete lists | | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Issue boards](project/issue_board.md):<br>Move issues between lists | | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Issues](project/issues/index.md):<br>Add Labels | ✓ (*15*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Issues](project/issues/index.md):<br>Add to epic | | ✓ (*23*) | ✓ (*23*) | ✓ (*23*) | ✓ (*23*) |
|
||||
| [Issues](project/issues/index.md):<br>Assign | ✓ (*15*) | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Issues](project/issues/index.md):<br>Create (*18*) | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| [Issues](project/issues/index.md):<br>Create [confidential issues](project/issues/confidential_issues.md) | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
|
@ -246,6 +247,7 @@ The following table lists project permissions available for each role:
|
|||
20. The ability to view the Container Registry and pull images is controlled by the [Container Registry's visibility permissions](packages/container_registry/index.md#container-registry-visibility-permissions).
|
||||
21. Maintainers cannot create, demote, or remove Owners, and they cannot promote users to the Owner role. They also cannot approve Owner role access requests.
|
||||
22. Authors of tasks can delete them even if they don't have the Owner role, but they have to have at least the Guest role for the project.
|
||||
23. You must have permission to [view the epic](group/epics/manage_epics.md#who-can-view-an-epic).
|
||||
|
||||
<!-- markdownlint-enable MD029 -->
|
||||
|
||||
|
@ -379,6 +381,7 @@ The following table lists group permissions available for each role:
|
|||
|
||||
| Action | Guest | Reporter | Developer | Maintainer | Owner |
|
||||
|-----------------------------------------------------------------------------------------|-------|----------|-----------|------------|-------|
|
||||
| Add an issue to an [epic](group/epics/index.md) | ✓ (7) | ✓ (7) | ✓ (7) | ✓ (7) | ✓ (7) |
|
||||
| Browse group | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| Pull a container image using the dependency proxy | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
| View Contribution analytics | ✓ | ✓ | ✓ | ✓ | ✓ |
|
||||
|
@ -445,6 +448,7 @@ The following table lists group permissions available for each role:
|
|||
4. Developers can push commits to the default branch of a new project only if the [default branch protection](group/manage.md#change-the-default-branch-protection-of-a-group) is set to "Partially protected" or "Not protected".
|
||||
5. In addition, if your group is public or internal, all users who can see the group can also see group wiki pages.
|
||||
6. Users can only view events based on their individual actions.
|
||||
7. You must have permission to [view the epic](group/epics/manage_epics.md#who-can-view-an-epic) and edit the issue.
|
||||
|
||||
<!-- markdownlint-enable MD029 -->
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
module API
|
||||
module Entities
|
||||
# Serializes a Licensee::License
|
||||
class License < Entities::LicenseBasic
|
||||
expose :popular?, as: :popular
|
||||
expose(:description) { |license| license.meta['description'] }
|
||||
|
|
|
@ -2,10 +2,16 @@
|
|||
|
||||
module API
|
||||
module Entities
|
||||
# Serializes a Gitlab::Git::DeclaredLicense
|
||||
class LicenseBasic < Grape::Entity
|
||||
expose :key, :name, :nickname
|
||||
expose :url, as: :html_url
|
||||
expose(:source_url) { |license| license.meta['source'] }
|
||||
|
||||
# This was dropped:
|
||||
# https://github.com/github/choosealicense.com/commit/325806b42aa3d5b78e84120327ec877bc936dbdd#diff-66df8f1997786f7052d29010f2cbb4c66391d60d24ca624c356acc0ab986f139
|
||||
expose :source_url do |_|
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,113 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
##
|
||||
# This class is a queuing system for processing expensive tasks in an atomic manner
|
||||
# with batch poping to let you optimize the total processing time.
|
||||
#
|
||||
# In usual queuing system, the first item started being processed immediately
|
||||
# and the following items wait until the next items have been popped from the queue.
|
||||
# On the other hand, this queueing system, the former part is same, however,
|
||||
# it pops the enqueued items as batch. This is especially useful when you want to
|
||||
# drop redundant items from the queue in order to process important items only,
|
||||
# thus it's more efficient than the traditional queueing system.
|
||||
#
|
||||
# Caveats:
|
||||
# - The order of the items are not guaranteed because of `sadd` (Redis Sets).
|
||||
#
|
||||
# Example:
|
||||
# ```
|
||||
# class TheWorker
|
||||
# def perform
|
||||
# result = Gitlab::BatchPopQueueing.new('feature', 'queue').safe_execute([item]) do |items_in_queue|
|
||||
# item = extract_the_most_important_item_from(items_in_queue)
|
||||
# expensive_process(item)
|
||||
# end
|
||||
#
|
||||
# if result[:status] == :finished && result[:new_items].present?
|
||||
# item = extract_the_most_important_item_from(items_in_queue)
|
||||
# TheWorker.perform_async(item.id)
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
# ```
|
||||
#
|
||||
class BatchPopQueueing
|
||||
attr_reader :namespace, :queue_id
|
||||
|
||||
EXTRA_QUEUE_EXPIRE_WINDOW = 1.hour
|
||||
MAX_COUNTS_OF_POP_ALL = 1000
|
||||
|
||||
# Initialize queue
|
||||
#
|
||||
# @param [String] namespace The namespace of the exclusive lock and queue key. Typically, it's a feature name.
|
||||
# @param [String] queue_id The identifier of the queue.
|
||||
# @return [Boolean]
|
||||
def initialize(namespace, queue_id)
|
||||
raise ArgumentError if namespace.empty? || queue_id.empty?
|
||||
|
||||
@namespace = namespace
|
||||
@queue_id = queue_id
|
||||
end
|
||||
|
||||
##
|
||||
# Execute the given block in an exclusive lock.
|
||||
# If there is the other thread has already working on the block,
|
||||
# it enqueues the items without processing the block.
|
||||
#
|
||||
# @param [Array<String>] new_items New items to be added to the queue.
|
||||
# @param [Time] lock_timeout The timeout of the exclusive lock. Generally, this value should be longer than the maximum prosess timing of the given block.
|
||||
# @return [Hash]
|
||||
# - status => One of the `:enqueued` or `:finished`.
|
||||
# - new_items => Newly enqueued items during the given block had been processed.
|
||||
#
|
||||
# NOTE: If an exception is raised in the block, the poppped items will not be recovered.
|
||||
# We should NOT re-enqueue the items in this case because it could end up in an infinite loop.
|
||||
def safe_execute(new_items, lock_timeout: 10.minutes, &block)
|
||||
enqueue(new_items, lock_timeout + EXTRA_QUEUE_EXPIRE_WINDOW)
|
||||
|
||||
lease = Gitlab::ExclusiveLease.new(lock_key, timeout: lock_timeout)
|
||||
|
||||
return { status: :enqueued } unless uuid = lease.try_obtain
|
||||
|
||||
begin
|
||||
all_args = pop_all
|
||||
|
||||
yield all_args if block
|
||||
|
||||
{ status: :finished, new_items: peek_all }
|
||||
ensure
|
||||
Gitlab::ExclusiveLease.cancel(lock_key, uuid)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lock_key
|
||||
@lock_key ||= "batch_pop_queueing:lock:#{namespace}:#{queue_id}"
|
||||
end
|
||||
|
||||
def queue_key
|
||||
@queue_key ||= "batch_pop_queueing:queue:#{namespace}:#{queue_id}"
|
||||
end
|
||||
|
||||
def enqueue(items, expire_time)
|
||||
Gitlab::Redis::Queues.with do |redis|
|
||||
redis.sadd(queue_key, items)
|
||||
redis.expire(queue_key, expire_time.to_i)
|
||||
end
|
||||
end
|
||||
|
||||
def pop_all
|
||||
Gitlab::Redis::Queues.with do |redis|
|
||||
redis.spop(queue_key, MAX_COUNTS_OF_POP_ALL)
|
||||
end
|
||||
end
|
||||
|
||||
def peek_all
|
||||
Gitlab::Redis::Queues.with do |redis|
|
||||
redis.smembers(queue_key)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -44,31 +44,15 @@ module Gitlab
|
|||
attr_reader :json_data, :report, :validate
|
||||
|
||||
def valid?
|
||||
# We want validation to happen regardless of VALIDATE_SCHEMA
|
||||
# CI variable.
|
||||
#
|
||||
# Previously it controlled BOTH validation and enforcement of
|
||||
# schema validation result.
|
||||
#
|
||||
# After 15.0 we will enforce schema validation by default
|
||||
# See: https://gitlab.com/groups/gitlab-org/-/epics/6968
|
||||
schema_validator.deprecation_warnings.each { |deprecation_warning| report.add_warning('Schema', deprecation_warning) }
|
||||
return true unless validate
|
||||
|
||||
if validate
|
||||
schema_validation_passed = schema_validator.valid?
|
||||
|
||||
# Validation warnings are errors
|
||||
schema_validator.errors.each { |error| report.add_error('Schema', error) }
|
||||
schema_validator.warnings.each { |warning| report.add_error('Schema', warning) }
|
||||
|
||||
schema_validation_passed
|
||||
else
|
||||
# Validation warnings are warnings
|
||||
schema_validator.errors.each { |error| report.add_warning('Schema', error) }
|
||||
schema_validator.deprecation_warnings.each { |deprecation_warning| report.add_warning('Schema', deprecation_warning) }
|
||||
schema_validator.warnings.each { |warning| report.add_warning('Schema', warning) }
|
||||
|
||||
true
|
||||
end
|
||||
schema_validation_passed
|
||||
end
|
||||
|
||||
def schema_validator
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Gitlab
|
||||
module Git
|
||||
# DeclaredLicense is the software license declared in a LICENSE or COPYING
|
||||
# file in the git repository.
|
||||
class DeclaredLicense
|
||||
# SPDX Identifier for the license
|
||||
attr_reader :key
|
||||
|
||||
# Full name of the license
|
||||
attr_reader :name
|
||||
|
||||
# Nickname of the license (optional, a shorter user-friendly name)
|
||||
attr_reader :nickname
|
||||
|
||||
# Filename of the file containing license
|
||||
attr_accessor :path
|
||||
|
||||
# URL that points to the LICENSE
|
||||
attr_reader :url
|
||||
|
||||
def initialize(key: nil, name: nil, nickname: nil, url: nil, path: nil)
|
||||
@key = key
|
||||
@name = name
|
||||
@nickname = nickname
|
||||
@url = url
|
||||
@path = path
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
return unless other.is_a?(self.class)
|
||||
|
||||
key == other.key
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -783,10 +783,29 @@ module Gitlab
|
|||
end
|
||||
end
|
||||
|
||||
def license_short_name
|
||||
def license(from_gitaly)
|
||||
wrapped_gitaly_errors do
|
||||
gitaly_repository_client.license_short_name
|
||||
response = gitaly_repository_client.find_license
|
||||
|
||||
break nil if response.license_short_name.empty?
|
||||
|
||||
if from_gitaly
|
||||
break ::Gitlab::Git::DeclaredLicense.new(key: response.license_short_name,
|
||||
name: response.license_name,
|
||||
nickname: response.license_nickname.presence,
|
||||
url: response.license_url.presence,
|
||||
path: response.license_path)
|
||||
end
|
||||
|
||||
licensee_object = Licensee::License.new(response.license_short_name)
|
||||
|
||||
break nil if licensee_object.name.blank?
|
||||
|
||||
licensee_object
|
||||
end
|
||||
rescue Licensee::InvalidLicense => e
|
||||
Gitlab::ErrorTracking.track_exception(e)
|
||||
nil
|
||||
end
|
||||
|
||||
def fetch_source_branch!(source_repository, source_branch, local_ref)
|
||||
|
|
|
@ -283,12 +283,10 @@ module Gitlab
|
|||
response.path.presence
|
||||
end
|
||||
|
||||
def license_short_name
|
||||
def find_license
|
||||
request = Gitaly::FindLicenseRequest.new(repository: @gitaly_repo)
|
||||
|
||||
response = GitalyClient.call(@storage, :repository_service, :find_license, request, timeout: GitalyClient.fast_timeout)
|
||||
|
||||
response.license_short_name.presence
|
||||
GitalyClient.call(@storage, :repository_service, :find_license, request, timeout: GitalyClient.fast_timeout)
|
||||
end
|
||||
|
||||
def calculate_checksum
|
||||
|
|
|
@ -30046,6 +30046,9 @@ msgstr ""
|
|||
msgid "Please use this form to report to the admin users who create spam issues, comments or behave inappropriately."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please wait a few moments while we load the file history for this line."
|
||||
msgstr ""
|
||||
|
||||
msgid "Please wait a moment, this page will automatically refresh when ready."
|
||||
msgstr ""
|
||||
|
||||
|
@ -44266,6 +44269,9 @@ msgstr ""
|
|||
msgid "Vulnerability|File"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|File:"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|GitLab Security Report"
|
||||
msgstr ""
|
||||
|
||||
|
@ -44290,6 +44296,9 @@ msgstr ""
|
|||
msgid "Vulnerability|Links"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Location"
|
||||
msgstr ""
|
||||
|
||||
msgid "Vulnerability|Method"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -299,14 +299,16 @@ RSpec.describe ProjectsController do
|
|||
end
|
||||
|
||||
it "renders files even with invalid license" do
|
||||
invalid_license = ::Gitlab::Git::DeclaredLicense.new(key: 'woozle', name: 'woozle wuzzle')
|
||||
|
||||
controller.instance_variable_set(:@project, public_project)
|
||||
expect(public_project.repository).to receive(:license_key).and_return('woozle wuzzle').at_least(:once)
|
||||
expect(public_project.repository).to receive(:license).and_return(invalid_license).at_least(:once)
|
||||
|
||||
get_show
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response).to render_template('_files')
|
||||
expect(response.body).to have_content('LICENSE') # would be 'MIT license' if stub not works
|
||||
expect(response.body).to have_content('woozle wuzzle')
|
||||
end
|
||||
|
||||
describe 'tracking events', :snowplow do
|
||||
|
|
|
@ -675,7 +675,7 @@ RSpec.describe 'File blob', :js do
|
|||
expect(page).to have_content('This project is licensed under the MIT License.')
|
||||
|
||||
# shows a learn more link
|
||||
expect(page).to have_link('Learn more', href: 'http://choosealicense.com/licenses/mit/')
|
||||
expect(page).to have_link('Learn more', href: 'https://opensource.org/licenses/MIT')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -171,7 +171,7 @@
|
|||
},
|
||||
"type": "sast",
|
||||
"status": "success",
|
||||
"start_time": "placeholder-value",
|
||||
"end_time": "placeholder-value"
|
||||
"start_time": "2022-08-10T22:37:00",
|
||||
"end_time": "2022-08-10T22:38:00"
|
||||
}
|
||||
}
|
|
@ -12,8 +12,29 @@
|
|||
"id": "gemnasium",
|
||||
"name": "Gemnasium"
|
||||
},
|
||||
"location": {},
|
||||
"identifiers": [],
|
||||
"location": {
|
||||
"file": "yarn/yarn.lock",
|
||||
"dependency": {
|
||||
"package": {
|
||||
"name": "io.netty/netty"
|
||||
},
|
||||
"version": "3.9.1.Final"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"value": "2017-11429",
|
||||
"type": "cwe",
|
||||
"name": "CWE-2017-11429",
|
||||
"url": "https://cve.mitre.org/cgi-bin/cwename.cgi?name=CWE-2017-11429"
|
||||
},
|
||||
{
|
||||
"value": "2017-11429",
|
||||
"type": "cve",
|
||||
"name": "CVE-2017-11429",
|
||||
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020"
|
||||
|
@ -33,8 +54,29 @@
|
|||
"id": "gemnasium",
|
||||
"name": "Gemnasium"
|
||||
},
|
||||
"location": {},
|
||||
"identifiers": [],
|
||||
"location": {
|
||||
"file": "yarn/yarn.lock",
|
||||
"dependency": {
|
||||
"package": {
|
||||
"name": "io.netty/netty"
|
||||
},
|
||||
"version": "3.9.1.Final"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"value": "2017-11429",
|
||||
"type": "cwe",
|
||||
"name": "CWE-2017-11429",
|
||||
"url": "https://cve.mitre.org/cgi-bin/cwename.cgi?name=CWE-2017-11429"
|
||||
},
|
||||
{
|
||||
"value": "2017-11429",
|
||||
"type": "cve",
|
||||
"name": "CVE-2017-11429",
|
||||
"url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{
|
||||
"name": "CVE-1030",
|
||||
|
@ -161,8 +203,9 @@
|
|||
"version": "2.18.0"
|
||||
},
|
||||
"type": "dependency_scanning",
|
||||
"start_time": "placeholder-value",
|
||||
"end_time": "placeholder-value",
|
||||
"start_time": "2022-08-10T21:37:00",
|
||||
"end_time": "2022-08-10T21:38:00",
|
||||
"status": "success"
|
||||
}
|
||||
},
|
||||
"version": "14.0.6"
|
||||
}
|
|
@ -12,7 +12,15 @@
|
|||
"id": "gemnasium",
|
||||
"name": "Gemnasium"
|
||||
},
|
||||
"location": {},
|
||||
"location": {
|
||||
"file": "some/kind/of/file.c",
|
||||
"dependency": {
|
||||
"package": {
|
||||
"name": "io.netty/netty"
|
||||
},
|
||||
"version": "3.9.1.Final"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "GitLab",
|
||||
|
@ -27,18 +35,8 @@
|
|||
],
|
||||
"details": {
|
||||
"commit": {
|
||||
"name": [
|
||||
{
|
||||
"lang": "en",
|
||||
"value": "The Commit"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"lang": "en",
|
||||
"value": "Commit where the vulnerability was identified"
|
||||
}
|
||||
],
|
||||
"name": "the commit",
|
||||
"description": "description",
|
||||
"type": "commit",
|
||||
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
|
||||
}
|
||||
|
@ -56,7 +54,15 @@
|
|||
"id": "gemnasium",
|
||||
"name": "Gemnasium"
|
||||
},
|
||||
"location": {},
|
||||
"location": {
|
||||
"file": "some/kind/of/file.c",
|
||||
"dependency": {
|
||||
"package": {
|
||||
"name": "io.netty/netty"
|
||||
},
|
||||
"version": "3.9.1.Final"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "GitLab",
|
||||
|
@ -71,18 +77,8 @@
|
|||
],
|
||||
"details": {
|
||||
"commit": {
|
||||
"name": [
|
||||
{
|
||||
"lang": "en",
|
||||
"value": "The Commit"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"lang": "en",
|
||||
"value": "Commit where the vulnerability was identified"
|
||||
}
|
||||
],
|
||||
"name": "the commit",
|
||||
"description": "description",
|
||||
"type": "commit",
|
||||
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
|
||||
}
|
||||
|
@ -100,7 +96,15 @@
|
|||
"id": "gemnasium",
|
||||
"name": "Gemnasium"
|
||||
},
|
||||
"location": {},
|
||||
"location": {
|
||||
"file": "some/kind/of/file.c",
|
||||
"dependency": {
|
||||
"package": {
|
||||
"name": "io.netty/netty"
|
||||
},
|
||||
"version": "3.9.1.Final"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "GitLab",
|
||||
|
@ -115,18 +119,8 @@
|
|||
],
|
||||
"details": {
|
||||
"commit": {
|
||||
"name": [
|
||||
{
|
||||
"lang": "en",
|
||||
"value": "The Commit"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"lang": "en",
|
||||
"value": "Commit where the vulnerability was identified"
|
||||
}
|
||||
],
|
||||
"name": "the commit",
|
||||
"description": "description",
|
||||
"type": "commit",
|
||||
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
|
||||
}
|
||||
|
@ -144,7 +138,15 @@
|
|||
"id": "gemnasium",
|
||||
"name": "Gemnasium"
|
||||
},
|
||||
"location": {},
|
||||
"location": {
|
||||
"file": "some/kind/of/file.c",
|
||||
"dependency": {
|
||||
"package": {
|
||||
"name": "io.netty/netty"
|
||||
},
|
||||
"version": "3.9.1.Final"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "GitLab",
|
||||
|
@ -159,18 +161,8 @@
|
|||
],
|
||||
"details": {
|
||||
"commit": {
|
||||
"name": [
|
||||
{
|
||||
"lang": "en",
|
||||
"value": "The Commit"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"lang": "en",
|
||||
"value": "Commit where the vulnerability was identified"
|
||||
}
|
||||
],
|
||||
"name": "the commit",
|
||||
"description": "description",
|
||||
"type": "commit",
|
||||
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
|
||||
}
|
||||
|
@ -258,7 +250,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"location": {},
|
||||
"location": {
|
||||
"file": "some/kind/of/file.c",
|
||||
"dependency": {
|
||||
"package": {
|
||||
"name": "io.netty/netty"
|
||||
},
|
||||
"version": "3.9.1.Final"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "GitLab",
|
||||
|
@ -273,18 +273,8 @@
|
|||
],
|
||||
"details": {
|
||||
"commit": {
|
||||
"name": [
|
||||
{
|
||||
"lang": "en",
|
||||
"value": "The Commit"
|
||||
}
|
||||
],
|
||||
"description": [
|
||||
{
|
||||
"lang": "en",
|
||||
"value": "Commit where the vulnerability was identified"
|
||||
}
|
||||
],
|
||||
"name": "the commit",
|
||||
"description": "description",
|
||||
"type": "commit",
|
||||
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
|
||||
}
|
||||
|
@ -373,7 +363,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"location": {},
|
||||
"location": {
|
||||
"file": "some/kind/of/file.c",
|
||||
"dependency": {
|
||||
"package": {
|
||||
"name": "io.netty/netty"
|
||||
},
|
||||
"version": "3.9.1.Final"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "GitLab",
|
||||
|
@ -400,8 +398,22 @@
|
|||
"id": "gemnasium",
|
||||
"name": "Gemnasium"
|
||||
},
|
||||
"location": {},
|
||||
"identifiers": [],
|
||||
"location": {
|
||||
"file": "some/kind/of/file.c",
|
||||
"dependency": {
|
||||
"package": {
|
||||
"name": "io.netty/netty"
|
||||
},
|
||||
"version": "3.9.1.Final"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
{
|
||||
"type": "GitLab",
|
||||
"name": "Foo vulnerability",
|
||||
"value": "foo"
|
||||
}
|
||||
],
|
||||
"links": []
|
||||
}
|
||||
],
|
||||
|
@ -442,8 +454,8 @@
|
|||
"cve": "CVE-1020"
|
||||
}
|
||||
],
|
||||
"summary": "",
|
||||
"diff": ""
|
||||
"summary": "this fixes CVE-1020",
|
||||
"diff": "dG90YWxseSBsZWdpdGltYXRlIGRpZmYsIDEwLzEwIHdvdWxkIGFwcGx5"
|
||||
},
|
||||
{
|
||||
"fixes": [
|
||||
|
@ -452,8 +464,8 @@
|
|||
"id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3"
|
||||
}
|
||||
],
|
||||
"summary": "",
|
||||
"diff": ""
|
||||
"summary": "this fixes CVE",
|
||||
"diff": "dG90YWxseSBsZWdpdGltYXRlIGRpZmYsIDEwLzEwIHdvdWxkIGFwcGx5"
|
||||
},
|
||||
{
|
||||
"fixes": [
|
||||
|
@ -462,8 +474,8 @@
|
|||
"id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3"
|
||||
}
|
||||
],
|
||||
"summary": "",
|
||||
"diff": ""
|
||||
"summary": "this fixed CVE",
|
||||
"diff": "dG90YWxseSBsZWdpdGltYXRlIGRpZmYsIDEwLzEwIHdvdWxkIGFwcGx5"
|
||||
},
|
||||
{
|
||||
"fixes": [
|
||||
|
@ -472,8 +484,8 @@
|
|||
"cve": "CVE-1"
|
||||
}
|
||||
],
|
||||
"summary": "",
|
||||
"diff": ""
|
||||
"summary": "this fixes CVE-1",
|
||||
"diff": "dG90YWxseSBsZWdpdGltYXRlIGRpZmYsIDEwLzEwIHdvdWxkIGFwcGx5"
|
||||
}
|
||||
],
|
||||
"dependency_files": [],
|
||||
|
@ -497,8 +509,8 @@
|
|||
"version": "2.18.0"
|
||||
},
|
||||
"type": "dependency_scanning",
|
||||
"start_time": "placeholder-value",
|
||||
"end_time": "placeholder-value",
|
||||
"start_time": "2022-08-10T21:37:00",
|
||||
"end_time": "2022-08-10T21:38:00",
|
||||
"status": "success"
|
||||
},
|
||||
"version": "14.0.2"
|
||||
|
|
|
@ -62,7 +62,7 @@
|
|||
},
|
||||
"type": "sast",
|
||||
"status": "success",
|
||||
"start_time": "placeholder-value",
|
||||
"end_time": "placeholder-value"
|
||||
"start_time": "2022-08-10T21:37:00",
|
||||
"end_time": "2022-08-10T21:38:00"
|
||||
}
|
||||
}
|
|
@ -194,7 +194,7 @@
|
|||
},
|
||||
"type": "sast",
|
||||
"status": "success",
|
||||
"start_time": "placeholder-value",
|
||||
"end_time": "placeholder-value"
|
||||
"start_time": "2022-08-10T21:37:00",
|
||||
"end_time": "2022-08-10T21:38:00"
|
||||
}
|
||||
}
|
|
@ -18,6 +18,9 @@
|
|||
"file": "aws-key.py",
|
||||
"dependency": {
|
||||
"package": {}
|
||||
},
|
||||
"commit": {
|
||||
"sha": "e9c3a56590d5bed4155c0d128f1552d52fdcc7ae"
|
||||
}
|
||||
},
|
||||
"identifiers": [
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
import redirectToCorrectPage from '~/blame/blame_redirect';
|
||||
import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures';
|
||||
import { createAlert } from '~/flash';
|
||||
|
||||
jest.mock('~/flash');
|
||||
|
||||
describe('Blame page redirect', () => {
|
||||
beforeEach(() => {
|
||||
global.window = Object.create(window);
|
||||
const url = 'https://gitlab.com/flightjs/Flight/-/blame/master/file.json';
|
||||
Object.defineProperty(window, 'location', {
|
||||
writable: true,
|
||||
value: {
|
||||
href: url,
|
||||
hash: '',
|
||||
search: '',
|
||||
},
|
||||
});
|
||||
|
||||
setHTMLFixture(`<div class="js-per-page" data-per-page="1000"></div>`);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
createAlert.mockClear();
|
||||
resetHTMLFixture();
|
||||
});
|
||||
|
||||
it('performs redirect to further pages when needed', () => {
|
||||
window.location.hash = '#L1001';
|
||||
redirectToCorrectPage();
|
||||
expect(window.location.href).toMatch('?page=2');
|
||||
});
|
||||
|
||||
it('performs redirect back to first page when needed', () => {
|
||||
window.location.href = 'https://gitlab.com/flightjs/Flight/-/blame/master/file.json';
|
||||
window.location.search = '?page=200';
|
||||
window.location.hash = '#L999';
|
||||
redirectToCorrectPage();
|
||||
expect(window.location.href).toMatch('?page=1');
|
||||
});
|
||||
|
||||
it('doesn`t perform redirect when the line is still on page 1', () => {
|
||||
window.location.hash = '#L1000';
|
||||
redirectToCorrectPage();
|
||||
expect(window.location.href).not.toMatch('?page');
|
||||
});
|
||||
|
||||
it('doesn`t perform redirect when "no_pagination" param is present', () => {
|
||||
window.location.href = 'https://gitlab.com/flightjs/Flight/-/blame/master/file.json';
|
||||
window.location.search = '?no_pagination=true';
|
||||
window.location.hash = '#L1001';
|
||||
redirectToCorrectPage();
|
||||
expect(window.location.href).not.toMatch('?page');
|
||||
});
|
||||
|
||||
it('doesn`t perform redirect when perPage is not present', () => {
|
||||
setHTMLFixture(`<div class="js-per-page"></div>`);
|
||||
window.location.hash = '#L1001';
|
||||
redirectToCorrectPage();
|
||||
expect(window.location.href).not.toMatch('?page');
|
||||
});
|
||||
|
||||
it('shows alert with a message', () => {
|
||||
window.location.hash = '#L1001';
|
||||
redirectToCorrectPage();
|
||||
expect(createAlert).toHaveBeenCalledWith({
|
||||
message: 'Please wait a few moments while we load the file history for this line.',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -8,8 +8,8 @@ import { defaultConfig, harborTagsList } from '../../mock_data';
|
|||
describe('Harbor tag list row', () => {
|
||||
let wrapper;
|
||||
|
||||
const findListItem = () => wrapper.find(ListItem);
|
||||
const findClipboardButton = () => wrapper.find(ClipboardButton);
|
||||
const findListItem = () => wrapper.findComponent(ListItem);
|
||||
const findClipboardButton = () => wrapper.findComponent(ClipboardButton);
|
||||
const findByTestId = (testId) => wrapper.findByTestId(testId);
|
||||
|
||||
const $route = {
|
||||
|
|
|
@ -8,9 +8,9 @@ import { defaultConfig, harborTagsResponse } from '../../mock_data';
|
|||
describe('Harbor Tags List', () => {
|
||||
let wrapper;
|
||||
|
||||
const findTagsLoader = () => wrapper.find(TagsLoader);
|
||||
const findTagsLoader = () => wrapper.findComponent(TagsLoader);
|
||||
const findTagsListRows = () => wrapper.findAllComponents(TagsListRow);
|
||||
const findRegistryList = () => wrapper.find(RegistryList);
|
||||
const findRegistryList = () => wrapper.findComponent(RegistryList);
|
||||
|
||||
const mountComponent = ({ propsData, config = defaultConfig }) => {
|
||||
wrapper = shallowMount(TagsList, {
|
||||
|
|
|
@ -15,8 +15,8 @@ jest.mock('~/rest_api', () => ({
|
|||
describe('Harbor Tags page', () => {
|
||||
let wrapper;
|
||||
|
||||
const findTagsHeader = () => wrapper.find(TagsHeader);
|
||||
const findTagsList = () => wrapper.find(TagsList);
|
||||
const findTagsHeader = () => wrapper.findComponent(TagsHeader);
|
||||
const findTagsList = () => wrapper.findComponent(TagsList);
|
||||
|
||||
const waitForHarborTagsRequest = async () => {
|
||||
await waitForPromises();
|
||||
|
|
|
@ -159,7 +159,7 @@ describe('Repository table component', () => {
|
|||
});
|
||||
|
||||
describe('Show more button', () => {
|
||||
const showMoreButton = () => vm.find(GlButton);
|
||||
const showMoreButton = () => vm.findComponent(GlButton);
|
||||
|
||||
it.each`
|
||||
hasMore | expectButtonToExist
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('Repository parent row component', () => {
|
|||
`('renders link in $path to $to', ({ path, to }) => {
|
||||
factory(path);
|
||||
|
||||
expect(vm.find(RouterLinkStub).props().to).toEqual({
|
||||
expect(vm.findComponent(RouterLinkStub).props().to).toEqual({
|
||||
path: to,
|
||||
});
|
||||
});
|
||||
|
@ -69,6 +69,6 @@ describe('Repository parent row component', () => {
|
|||
it('renders loading icon when loading parent', () => {
|
||||
factory('app/assets', 'app');
|
||||
|
||||
expect(vm.find(GlLoadingIcon).exists()).toBe(true);
|
||||
expect(vm.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -47,7 +47,7 @@ function factory(propsData = {}) {
|
|||
}
|
||||
|
||||
describe('Repository table row component', () => {
|
||||
const findRouterLink = () => vm.find(RouterLinkStub);
|
||||
const findRouterLink = () => vm.findComponent(RouterLinkStub);
|
||||
const findIntersectionObserver = () => vm.findComponent(GlIntersectionObserver);
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -124,7 +124,7 @@ describe('Repository table row component', () => {
|
|||
});
|
||||
|
||||
await nextTick();
|
||||
expect(vm.find(component).exists()).toBe(true);
|
||||
expect(vm.findComponent(component).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it.each`
|
||||
|
@ -141,7 +141,7 @@ describe('Repository table row component', () => {
|
|||
});
|
||||
|
||||
await nextTick();
|
||||
expect(vm.find({ ref: 'link' }).props('to')).toEqual({
|
||||
expect(vm.findComponent({ ref: 'link' }).props('to')).toEqual({
|
||||
path: `/-/tree/main/${encodeURIComponent(path)}`,
|
||||
});
|
||||
});
|
||||
|
@ -197,7 +197,7 @@ describe('Repository table row component', () => {
|
|||
});
|
||||
|
||||
await nextTick();
|
||||
expect(vm.find(GlBadge).exists()).toBe(true);
|
||||
expect(vm.findComponent(GlBadge).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('renders commit and web links with href for submodule', async () => {
|
||||
|
@ -213,7 +213,7 @@ describe('Repository table row component', () => {
|
|||
|
||||
await nextTick();
|
||||
expect(vm.find('a').attributes('href')).toEqual('https://test.com');
|
||||
expect(vm.find(GlLink).attributes('href')).toEqual('https://test.com/commit');
|
||||
expect(vm.findComponent(GlLink).attributes('href')).toEqual('https://test.com/commit');
|
||||
});
|
||||
|
||||
it('renders lock icon', async () => {
|
||||
|
@ -226,8 +226,8 @@ describe('Repository table row component', () => {
|
|||
});
|
||||
|
||||
await nextTick();
|
||||
expect(vm.find(GlIcon).exists()).toBe(true);
|
||||
expect(vm.find(GlIcon).props('name')).toBe('lock');
|
||||
expect(vm.findComponent(GlIcon).exists()).toBe(true);
|
||||
expect(vm.findComponent(GlIcon).props('name')).toBe('lock');
|
||||
});
|
||||
|
||||
it('renders loading icon when path is loading', () => {
|
||||
|
@ -240,7 +240,7 @@ describe('Repository table row component', () => {
|
|||
loadingPath: 'test',
|
||||
});
|
||||
|
||||
expect(vm.find(FileIcon).props('loading')).toBe(true);
|
||||
expect(vm.findComponent(FileIcon).props('loading')).toBe(true);
|
||||
});
|
||||
|
||||
describe('row visibility', () => {
|
||||
|
|
|
@ -38,7 +38,7 @@ function factory(path, data = () => ({})) {
|
|||
}
|
||||
|
||||
describe('Repository table component', () => {
|
||||
const findFileTable = () => vm.find(FileTable);
|
||||
const findFileTable = () => vm.findComponent(FileTable);
|
||||
|
||||
afterEach(() => {
|
||||
vm.destroy();
|
||||
|
@ -53,7 +53,7 @@ describe('Repository table component', () => {
|
|||
|
||||
await nextTick();
|
||||
|
||||
expect(vm.find(FilePreview).exists()).toBe(true);
|
||||
expect(vm.findComponent(FilePreview).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('trigger fetchFiles and resetRequestedCommits when mounted', async () => {
|
||||
|
|
|
@ -47,12 +47,12 @@ describe('UploadBlobModal', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const findModal = () => wrapper.find(GlModal);
|
||||
const findAlert = () => wrapper.find(GlAlert);
|
||||
const findCommitMessage = () => wrapper.find(GlFormTextarea);
|
||||
const findBranchName = () => wrapper.find(GlFormInput);
|
||||
const findMrToggle = () => wrapper.find(GlToggle);
|
||||
const findUploadDropzone = () => wrapper.find(UploadDropzone);
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findAlert = () => wrapper.findComponent(GlAlert);
|
||||
const findCommitMessage = () => wrapper.findComponent(GlFormTextarea);
|
||||
const findBranchName = () => wrapper.findComponent(GlFormInput);
|
||||
const findMrToggle = () => wrapper.findComponent(GlToggle);
|
||||
const findUploadDropzone = () => wrapper.findComponent(UploadDropzone);
|
||||
const actionButtonDisabledState = () => findModal().props('actionPrimary').attributes[0].disabled;
|
||||
const cancelButtonDisabledState = () => findModal().props('actionCancel').attributes[0].disabled;
|
||||
const actionButtonLoadingState = () => findModal().props('actionPrimary').attributes[0].loading;
|
||||
|
|
|
@ -7,7 +7,7 @@ jest.mock('~/repository/utils/dom');
|
|||
describe('Repository blob page component', () => {
|
||||
let wrapper;
|
||||
|
||||
const findBlobContentViewer = () => wrapper.find(BlobContentViewer);
|
||||
const findBlobContentViewer = () => wrapper.findComponent(BlobContentViewer);
|
||||
const path = 'file.js';
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('Repository index page component', () => {
|
|||
it('renders TreePage', () => {
|
||||
factory();
|
||||
|
||||
const child = wrapper.find(TreePage);
|
||||
const child = wrapper.findComponent(TreePage);
|
||||
|
||||
expect(child.exists()).toBe(true);
|
||||
expect(child.props()).toEqual({ path: '/' });
|
||||
|
|
|
@ -195,7 +195,7 @@ describe('AdminRunnersApp', () => {
|
|||
const { id, shortSha } = mockRunners[0];
|
||||
const numericId = getIdFromGraphQLId(id);
|
||||
|
||||
const runnerLink = wrapper.find('tr [data-testid="td-summary"]').find(GlLink);
|
||||
const runnerLink = wrapper.find('tr [data-testid="td-summary"]').findComponent(GlLink);
|
||||
|
||||
expect(runnerLink.text()).toBe(`#${numericId} (${shortSha})`);
|
||||
expect(runnerLink.attributes('href')).toBe(`http://localhost/admin/runners/${numericId}`);
|
||||
|
@ -204,7 +204,9 @@ describe('AdminRunnersApp', () => {
|
|||
it('renders runner actions for each runner', async () => {
|
||||
await createComponent({ mountFn: mountExtended });
|
||||
|
||||
const runnerActions = wrapper.find('tr [data-testid="td-actions"]').find(RunnerActionsCell);
|
||||
const runnerActions = wrapper
|
||||
.find('tr [data-testid="td-actions"]')
|
||||
.findComponent(RunnerActionsCell);
|
||||
const runner = mockRunners[0];
|
||||
|
||||
expect(runnerActions.props()).toEqual({
|
||||
|
@ -255,7 +257,7 @@ describe('AdminRunnersApp', () => {
|
|||
});
|
||||
|
||||
it('Links to the runner page', async () => {
|
||||
const runnerLink = wrapper.find('tr [data-testid="td-summary"]').find(GlLink);
|
||||
const runnerLink = wrapper.find('tr [data-testid="td-summary"]').findComponent(GlLink);
|
||||
|
||||
expect(runnerLink.text()).toBe(`#${id} (${shortSha})`);
|
||||
expect(runnerLink.attributes('href')).toBe(`http://localhost/admin/runners/${id}`);
|
||||
|
|
|
@ -5,7 +5,7 @@ import LinkCell from '~/runner/components/cells/link_cell.vue';
|
|||
describe('LinkCell', () => {
|
||||
let wrapper;
|
||||
|
||||
const findGlLink = () => wrapper.find(GlLink);
|
||||
const findGlLink = () => wrapper.findComponent(GlLink);
|
||||
const findSpan = () => wrapper.find('span');
|
||||
|
||||
const createComponent = ({ props = {}, ...options } = {}) => {
|
||||
|
|
|
@ -85,7 +85,7 @@ describe('RunnerTypeCell', () => {
|
|||
contactedAt: '2022-01-02',
|
||||
});
|
||||
|
||||
expect(findRunnerSummaryField('clock').find(TimeAgo).props('time')).toBe('2022-01-02');
|
||||
expect(findRunnerSummaryField('clock').findComponent(TimeAgo).props('time')).toBe('2022-01-02');
|
||||
});
|
||||
|
||||
it('Displays empty last contact', () => {
|
||||
|
@ -93,7 +93,7 @@ describe('RunnerTypeCell', () => {
|
|||
contactedAt: null,
|
||||
});
|
||||
|
||||
expect(findRunnerSummaryField('clock').find(TimeAgo).exists()).toBe(false);
|
||||
expect(findRunnerSummaryField('clock').findComponent(TimeAgo).exists()).toBe(false);
|
||||
expect(findRunnerSummaryField('clock').text()).toContain(__('Never'));
|
||||
});
|
||||
|
||||
|
@ -134,7 +134,7 @@ describe('RunnerTypeCell', () => {
|
|||
});
|
||||
|
||||
it('Displays created at', () => {
|
||||
expect(findRunnerSummaryField('calendar').find(TimeAgo).props('time')).toBe(
|
||||
expect(findRunnerSummaryField('calendar').findComponent(TimeAgo).props('time')).toBe(
|
||||
mockRunner.createdAt,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -113,7 +113,7 @@ describe('RunnerTypeTabs', () => {
|
|||
});
|
||||
|
||||
findTabs().wrappers.forEach((tab) => {
|
||||
expect(tab.find(RunnerCount).props()).toEqual({
|
||||
expect(tab.findComponent(RunnerCount).props()).toEqual({
|
||||
scope: INSTANCE_TYPE,
|
||||
skip: false,
|
||||
variables: expect.objectContaining(mockVariables),
|
||||
|
|
|
@ -145,7 +145,7 @@ describe('RunnerUpdateForm', () => {
|
|||
});
|
||||
|
||||
it('Form skeleton is shown', () => {
|
||||
expect(wrapper.find(GlSkeletonLoader).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
|
||||
expect(findFields()).toHaveLength(0);
|
||||
});
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('ConfidentialityFilter', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
const findRadioFilter = () => wrapper.find(RadioFilter);
|
||||
const findRadioFilter = () => wrapper.findComponent(RadioFilter);
|
||||
|
||||
describe('template', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('RadioFilter', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
const findGlRadioButtonGroup = () => wrapper.find(GlFormRadioGroup);
|
||||
const findGlRadioButtonGroup = () => wrapper.findComponent(GlFormRadioGroup);
|
||||
const findGlRadioButtons = () => findGlRadioButtonGroup().findAllComponents(GlFormRadio);
|
||||
const findGlRadioButtonsText = () => findGlRadioButtons().wrappers.map((w) => w.text());
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('StatusFilter', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
const findRadioFilter = () => wrapper.find(RadioFilter);
|
||||
const findRadioFilter = () => wrapper.findComponent(RadioFilter);
|
||||
|
||||
describe('template', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -43,9 +43,9 @@ describe('GlobalSearchSort', () => {
|
|||
wrapper = null;
|
||||
});
|
||||
|
||||
const findSortButtonGroup = () => wrapper.find(GlButtonGroup);
|
||||
const findSortDropdown = () => wrapper.find(GlDropdown);
|
||||
const findSortDirectionButton = () => wrapper.find(GlButton);
|
||||
const findSortButtonGroup = () => wrapper.findComponent(GlButtonGroup);
|
||||
const findSortDropdown = () => wrapper.findComponent(GlDropdown);
|
||||
const findSortDirectionButton = () => wrapper.findComponent(GlButton);
|
||||
const findDropdownItems = () => findSortDropdown().findAllComponents(GlDropdownItem);
|
||||
const findDropdownItemsText = () => findDropdownItems().wrappers.map((w) => w.text());
|
||||
|
||||
|
|
|
@ -36,9 +36,9 @@ describe('GlobalSearchTopbar', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findGlSearchBox = () => wrapper.find(GlSearchBoxByClick);
|
||||
const findGroupFilter = () => wrapper.find(GroupFilter);
|
||||
const findProjectFilter = () => wrapper.find(ProjectFilter);
|
||||
const findGlSearchBox = () => wrapper.findComponent(GlSearchBoxByClick);
|
||||
const findGroupFilter = () => wrapper.findComponent(GroupFilter);
|
||||
const findProjectFilter = () => wrapper.findComponent(ProjectFilter);
|
||||
|
||||
describe('template', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -53,7 +53,7 @@ describe('GroupFilter', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findSearchableDropdown = () => wrapper.find(SearchableDropdown);
|
||||
const findSearchableDropdown = () => wrapper.findComponent(SearchableDropdown);
|
||||
|
||||
describe('template', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -53,7 +53,7 @@ describe('ProjectFilter', () => {
|
|||
wrapper.destroy();
|
||||
});
|
||||
|
||||
const findSearchableDropdown = () => wrapper.find(SearchableDropdown);
|
||||
const findSearchableDropdown = () => wrapper.findComponent(SearchableDropdown);
|
||||
|
||||
describe('template', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -46,8 +46,8 @@ describe('search_settings/components/search_settings.vue', () => {
|
|||
};
|
||||
|
||||
const findMatchSiblingElement = () => document.querySelector(`[data-testid="sibling"]`);
|
||||
const findSearchBox = () => wrapper.find(GlSearchBoxByType);
|
||||
const findEmptyState = () => wrapper.find(GlEmptyState);
|
||||
const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
|
||||
const findEmptyState = () => wrapper.findComponent(GlEmptyState);
|
||||
const findHideWhenEmpty = () => document.querySelector(`.${HIDE_WHEN_EMPTY_CLASS}`);
|
||||
const search = (term) => {
|
||||
findSearchBox().vm.$emit('input', term);
|
||||
|
|
|
@ -193,7 +193,7 @@ describe('TrainingProviderList component', () => {
|
|||
});
|
||||
|
||||
it(`shows the learn more link for enabled card ${index}`, () => {
|
||||
const learnMoreLink = findCards().at(index).find(GlLink);
|
||||
const learnMoreLink = findCards().at(index).findComponent(GlLink);
|
||||
const tempLogo = TEMP_PROVIDER_URLS[name];
|
||||
|
||||
if (tempLogo) {
|
||||
|
@ -224,7 +224,7 @@ describe('TrainingProviderList component', () => {
|
|||
});
|
||||
|
||||
it('shows a info-tooltip that describes the purpose of a primary provider', () => {
|
||||
const infoIcon = findPrimaryProviderRadios().at(index).find(GlIcon);
|
||||
const infoIcon = findPrimaryProviderRadios().at(index).findComponent(GlIcon);
|
||||
const tooltip = getBinding(infoIcon.element, 'gl-tooltip');
|
||||
|
||||
expect(infoIcon.props()).toMatchObject({
|
||||
|
|
|
@ -42,7 +42,7 @@ describe('self monitor component', () => {
|
|||
it('renders as an expand button by default', () => {
|
||||
wrapper = shallowMount(SelfMonitor, { store });
|
||||
|
||||
const button = wrapper.find(GlButton);
|
||||
const button = wrapper.findComponent(GlButton);
|
||||
|
||||
expect(button.text()).toBe('Expand');
|
||||
});
|
||||
|
@ -79,7 +79,7 @@ describe('self monitor component', () => {
|
|||
wrapper = shallowMount(SelfMonitor, { store });
|
||||
|
||||
expect(
|
||||
wrapper.find({ ref: 'selfMonitoringFormText' }).find('a').attributes('href'),
|
||||
wrapper.findComponent({ ref: 'selfMonitoringFormText' }).find('a').attributes('href'),
|
||||
).toEqual(`${TEST_HOST}/instance-administrators-random/gitlab-self-monitoring`);
|
||||
});
|
||||
|
||||
|
|
|
@ -51,11 +51,11 @@ describe('SetStatusModalWrapper', () => {
|
|||
});
|
||||
};
|
||||
|
||||
const findModal = () => wrapper.find(GlModal);
|
||||
const findModal = () => wrapper.findComponent(GlModal);
|
||||
const findMessageField = () =>
|
||||
wrapper.findByPlaceholderText(SetStatusForm.i18n.statusMessagePlaceholder);
|
||||
const findClearStatusButton = () => wrapper.find('.js-clear-user-status-button');
|
||||
const findAvailabilityCheckbox = () => wrapper.find(GlFormCheckbox);
|
||||
const findAvailabilityCheckbox = () => wrapper.findComponent(GlFormCheckbox);
|
||||
const findClearStatusAtMessage = () => wrapper.find('[data-testid="clear-status-at-message"]');
|
||||
const getEmojiPicker = () => wrapper.findComponent(EmojiPickerStub);
|
||||
|
||||
|
|
|
@ -85,7 +85,7 @@ describe('AssigneeTitle component', () => {
|
|||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders spinner when loading', () => {
|
||||
|
@ -95,7 +95,7 @@ describe('AssigneeTitle component', () => {
|
|||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render edit link when not editable', () => {
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('Assignee component', () => {
|
|||
it('displays no assignee icon when collapsed', () => {
|
||||
createWrapper();
|
||||
const collapsedChildren = findCollapsedChildren();
|
||||
const userIcon = collapsedChildren.at(0).find(GlIcon);
|
||||
const userIcon = collapsedChildren.at(0).findComponent(GlIcon);
|
||||
|
||||
expect(collapsedChildren.length).toBe(1);
|
||||
expect(collapsedChildren.at(0).attributes('aria-label')).toBe('None');
|
||||
|
|
|
@ -46,7 +46,7 @@ describe('AssigneeAvatarLink component', () => {
|
|||
it('renders assignee avatar', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find(AssigneeAvatar).props()).toEqual(
|
||||
expect(wrapper.findComponent(AssigneeAvatar).props()).toEqual(
|
||||
expect.objectContaining({
|
||||
issuableType: TEST_ISSUABLE_TYPE,
|
||||
user: userDataMock(),
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('CollapsedAssigneeList component', () => {
|
|||
});
|
||||
}
|
||||
|
||||
const findNoUsersIcon = () => wrapper.find(GlIcon);
|
||||
const findNoUsersIcon = () => wrapper.findComponent(GlIcon);
|
||||
const findAvatarCounter = () => wrapper.find('.avatar-counter');
|
||||
const findAssignees = () => wrapper.findAllComponents(CollapsedAssignee);
|
||||
const getTooltipTitle = () => wrapper.attributes('title');
|
||||
|
|
|
@ -34,7 +34,7 @@ describe('CollapsedAssignee assignee component', () => {
|
|||
it('has assignee avatar', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find(AssigneeAvatar).props()).toEqual({
|
||||
expect(wrapper.findComponent(AssigneeAvatar).props()).toEqual({
|
||||
imgSize: 24,
|
||||
user: TEST_USER,
|
||||
issuableType: TEST_ISSUABLE_TYPE,
|
||||
|
|
|
@ -46,7 +46,7 @@ describe('UncollapsedAssigneeList component', () => {
|
|||
});
|
||||
|
||||
it('calls the AssigneeAvatarLink with the proper props', () => {
|
||||
expect(wrapper.find(AssigneeAvatarLink).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(AssigneeAvatarLink).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('Shows one user with avatar, username and author name', () => {
|
||||
|
|
|
@ -12,6 +12,6 @@ describe('CopyEmailToClipboard component', () => {
|
|||
});
|
||||
|
||||
it('sets CopyableField `value` prop to issueEmailAddress', () => {
|
||||
expect(wrapper.find(CopyableField).props('value')).toBe(mockIssueEmailAddress);
|
||||
expect(wrapper.findComponent(CopyableField).props('value')).toBe(mockIssueEmailAddress);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,7 +28,7 @@ describe('Sidebar date Widget', () => {
|
|||
|
||||
const findEditableItem = () => wrapper.findComponent(SidebarEditableItem);
|
||||
const findPopoverIcon = () => wrapper.find('[data-testid="inherit-date-popover"]');
|
||||
const findDatePicker = () => wrapper.find(GlDatepicker);
|
||||
const findDatePicker = () => wrapper.findComponent(GlDatepicker);
|
||||
|
||||
const createComponent = ({
|
||||
dueDateQueryHandler = jest.fn().mockResolvedValue(issuableDueDateResponse()),
|
||||
|
@ -149,14 +149,14 @@ describe('Sidebar date Widget', () => {
|
|||
createComponent({ canInherit });
|
||||
await waitForPromises();
|
||||
|
||||
expect(wrapper.find(component).exists()).toBe(expected);
|
||||
expect(wrapper.findComponent(component).exists()).toBe(expected);
|
||||
},
|
||||
);
|
||||
|
||||
it('does not render SidebarInheritDate when canInherit is true and date is loading', async () => {
|
||||
createComponent({ canInherit: true });
|
||||
|
||||
expect(wrapper.find(SidebarInheritDate).exists()).toBe(false);
|
||||
expect(wrapper.findComponent(SidebarInheritDate).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('displays a flash message when query is rejected', async () => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import SidebarFormattedDate from '~/sidebar/components/date/sidebar_formatted_da
|
|||
describe('SidebarFormattedDate', () => {
|
||||
let wrapper;
|
||||
const findFormattedDate = () => wrapper.find("[data-testid='sidebar-date-value']");
|
||||
const findRemoveButton = () => wrapper.find(GlButton);
|
||||
const findRemoveButton = () => wrapper.findComponent(GlButton);
|
||||
|
||||
const createComponent = ({ hasDate = true } = {}) => {
|
||||
wrapper = shallowMount(SidebarFormattedDate, {
|
||||
|
|
|
@ -21,7 +21,7 @@ describe('SeverityToken', () => {
|
|||
}
|
||||
});
|
||||
|
||||
const findIcon = () => wrapper.find(GlIcon);
|
||||
const findIcon = () => wrapper.findComponent(GlIcon);
|
||||
|
||||
it('renders severity token for each severity type', () => {
|
||||
Object.values(INCIDENT_SEVERITY).forEach((severity) => {
|
||||
|
|
|
@ -59,7 +59,7 @@ describe('SidebarSeverity', () => {
|
|||
const findCriticalSeverityDropdownItem = () => wrapper.findComponent(GlDropdownItem);
|
||||
const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
|
||||
const findTooltip = () => wrapper.findComponent(GlTooltip);
|
||||
const findCollapsedSeverity = () => wrapper.find({ ref: 'severity' });
|
||||
const findCollapsedSeverity = () => wrapper.findComponent({ ref: 'severity' });
|
||||
|
||||
describe('Severity widget', () => {
|
||||
it('renders severity dropdown and token', () => {
|
||||
|
|
|
@ -97,13 +97,13 @@ describe('Sidebar Todo Widget', () => {
|
|||
});
|
||||
|
||||
it('shows add todo icon', () => {
|
||||
expect(wrapper.find(GlIcon).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(GlIcon).exists()).toBe(true);
|
||||
|
||||
expect(wrapper.find(GlIcon).props('name')).toBe('todo-add');
|
||||
expect(wrapper.findComponent(GlIcon).props('name')).toBe('todo-add');
|
||||
});
|
||||
|
||||
it('sets default tooltip title', () => {
|
||||
expect(wrapper.find(GlButton).attributes('title')).toBe('Add a to do');
|
||||
expect(wrapper.findComponent(GlButton).attributes('title')).toBe('Add a to do');
|
||||
});
|
||||
|
||||
it('when user has a to do', async () => {
|
||||
|
@ -112,12 +112,12 @@ describe('Sidebar Todo Widget', () => {
|
|||
});
|
||||
|
||||
await waitForPromises();
|
||||
expect(wrapper.find(GlIcon).props('name')).toBe('todo-done');
|
||||
expect(wrapper.find(GlButton).attributes('title')).toBe('Mark as done');
|
||||
expect(wrapper.findComponent(GlIcon).props('name')).toBe('todo-done');
|
||||
expect(wrapper.findComponent(GlButton).attributes('title')).toBe('Mark as done');
|
||||
});
|
||||
|
||||
it('emits `todoUpdated` event on click on icon', async () => {
|
||||
wrapper.find(GlIcon).vm.$emit('click', event);
|
||||
wrapper.findComponent(GlIcon).vm.$emit('click', event);
|
||||
|
||||
await nextTick();
|
||||
expect(wrapper.emitted('todoUpdated')).toEqual([[false]]);
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('IssuableAssignees', () => {
|
|||
},
|
||||
});
|
||||
};
|
||||
const findUncollapsedAssigneeList = () => wrapper.find(UncollapsedAssigneeList);
|
||||
const findUncollapsedAssigneeList = () => wrapper.findComponent(UncollapsedAssigneeList);
|
||||
const findEmptyAssignee = () => wrapper.find('[data-testid="none"]');
|
||||
|
||||
afterEach(() => {
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('IssuableLockForm', () => {
|
|||
const findSidebarCollapseIcon = () => wrapper.find('[data-testid="sidebar-collapse-icon"]');
|
||||
const findLockStatus = () => wrapper.find('[data-testid="lock-status"]');
|
||||
const findEditLink = () => wrapper.find('[data-testid="edit-link"]');
|
||||
const findEditForm = () => wrapper.find(EditForm);
|
||||
const findEditForm = () => wrapper.findComponent(EditForm);
|
||||
const findSidebarLockStatusTooltip = () =>
|
||||
getBinding(findSidebarCollapseIcon().element, 'gl-tooltip');
|
||||
|
||||
|
|
|
@ -36,7 +36,7 @@ describe('Participants', () => {
|
|||
loading: true,
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not show loading spinner not loading', () => {
|
||||
|
@ -44,7 +44,7 @@ describe('Participants', () => {
|
|||
loading: false,
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('shows participant count when given', () => {
|
||||
|
@ -73,7 +73,7 @@ describe('Participants', () => {
|
|||
loading: true,
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('when only showing visible participants, shows an avatar only for each participant under the limit', async () => {
|
||||
|
|
|
@ -47,7 +47,7 @@ describe('ReviewerTitle component', () => {
|
|||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
|
||||
});
|
||||
|
||||
it('renders spinner when loading', () => {
|
||||
|
@ -57,7 +57,7 @@ describe('ReviewerTitle component', () => {
|
|||
editable: false,
|
||||
});
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('does not render edit link when not editable', () => {
|
||||
|
|
|
@ -43,7 +43,7 @@ describe('Reviewer component', () => {
|
|||
it('displays no reviewer icon when collapsed', () => {
|
||||
createWrapper();
|
||||
const collapsedChildren = findCollapsedChildren();
|
||||
const userIcon = collapsedChildren.at(0).find(GlIcon);
|
||||
const userIcon = collapsedChildren.at(0).findComponent(GlIcon);
|
||||
|
||||
expect(collapsedChildren.length).toBe(1);
|
||||
expect(collapsedChildren.at(0).attributes('aria-label')).toBe('None');
|
||||
|
|
|
@ -73,19 +73,19 @@ describe('sidebar assignees', () => {
|
|||
it('hides assignees until fetched', async () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find(Assigness).exists()).toBe(false);
|
||||
expect(wrapper.findComponent(Assigness).exists()).toBe(false);
|
||||
|
||||
wrapper.vm.store.isFetching.assignees = false;
|
||||
|
||||
await nextTick();
|
||||
expect(wrapper.find(Assigness).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(Assigness).exists()).toBe(true);
|
||||
});
|
||||
|
||||
describe('when issuableType is issue', () => {
|
||||
it('finds AssigneesRealtime component', () => {
|
||||
createComponent();
|
||||
|
||||
expect(wrapper.find(AssigneesRealtime).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(AssigneesRealtime).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -93,7 +93,7 @@ describe('sidebar assignees', () => {
|
|||
it('does not find AssigneesRealtime component', () => {
|
||||
createComponent({ issuableType: 'MR' });
|
||||
|
||||
expect(wrapper.find(AssigneesRealtime).exists()).toBe(false);
|
||||
expect(wrapper.findComponent(AssigneesRealtime).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -108,7 +108,7 @@ describe('Subscriptions', () => {
|
|||
expect(wrapper.findByTestId('subscription-title').text()).toContain(
|
||||
subscribeDisabledDescription,
|
||||
);
|
||||
expect(wrapper.find({ ref: 'tooltip' }).attributes('title')).toBe(
|
||||
expect(wrapper.findComponent({ ref: 'tooltip' }).attributes('title')).toBe(
|
||||
subscribeDisabledDescription,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -43,8 +43,8 @@ describe('SidebarTodo', () => {
|
|||
({ isTodo, iconClass, label, icon }) => {
|
||||
createComponent({ isTodo });
|
||||
|
||||
expect(wrapper.find(GlIcon).classes().join(' ')).toStrictEqual(iconClass);
|
||||
expect(wrapper.find(GlIcon).props('name')).toStrictEqual(icon);
|
||||
expect(wrapper.findComponent(GlIcon).classes().join(' ')).toStrictEqual(iconClass);
|
||||
expect(wrapper.findComponent(GlIcon).props('name')).toStrictEqual(icon);
|
||||
expect(wrapper.find('button').text()).toBe(label);
|
||||
},
|
||||
);
|
||||
|
@ -76,19 +76,19 @@ describe('SidebarTodo', () => {
|
|||
it('renders button icon when `collapsed` prop is `true`', () => {
|
||||
createComponent({ collapsed: true });
|
||||
|
||||
expect(wrapper.find(GlIcon).props('name')).toBe('todo-done');
|
||||
expect(wrapper.findComponent(GlIcon).props('name')).toBe('todo-done');
|
||||
});
|
||||
|
||||
it('renders loading icon when `isActionActive` prop is true', () => {
|
||||
createComponent({ isActionActive: true });
|
||||
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
|
||||
});
|
||||
|
||||
it('hides button icon when `isActionActive` prop is true', () => {
|
||||
createComponent({ collapsed: true, isActionActive: true });
|
||||
|
||||
expect(wrapper.find(GlIcon).isVisible()).toBe(false);
|
||||
expect(wrapper.findComponent(GlIcon).isVisible()).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -206,7 +206,7 @@ describe('Snippet Edit app', () => {
|
|||
});
|
||||
|
||||
it('should hide loader', () => {
|
||||
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
|
||||
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -237,7 +237,7 @@ describe('Snippet Edit app', () => {
|
|||
!titleHasErrors,
|
||||
);
|
||||
|
||||
expect(wrapper.find(SnippetBlobActionsEdit).props('isValid')).toEqual(
|
||||
expect(wrapper.findComponent(SnippetBlobActionsEdit).props('isValid')).toEqual(
|
||||
!blobActionsHasErrors,
|
||||
);
|
||||
},
|
||||
|
@ -273,7 +273,7 @@ describe('Snippet Edit app', () => {
|
|||
selectedLevel: visibility,
|
||||
});
|
||||
|
||||
expect(wrapper.find(SnippetVisibilityEdit).props('value')).toBe(visibility);
|
||||
expect(wrapper.findComponent(SnippetVisibilityEdit).props('value')).toBe(visibility);
|
||||
});
|
||||
|
||||
describe('form submission handling', () => {
|
||||
|
|
|
@ -36,7 +36,7 @@ describe('snippets/components/embed_dropdown', () => {
|
|||
|
||||
sections.push(current);
|
||||
} else {
|
||||
const value = x.find(GlFormInputGroup).props('value');
|
||||
const value = x.findComponent(GlFormInputGroup).props('value');
|
||||
const copyValue = x.find('button[title="Copy"]').attributes('data-clipboard-text');
|
||||
|
||||
Object.assign(current, {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue