Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-09-21 15:14:09 +00:00
parent c0a3d287c0
commit e87220d9c1
155 changed files with 1288 additions and 1021 deletions

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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 });
}
}
}

View File

@ -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.",
"minLength": 1
},
"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,9 +1600,22 @@
},
"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": {
"type": "string"
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "array",
"minLength": 1,
"items": {
"type": "string"
}
}
]
}
}
}

View File

@ -1,3 +1,5 @@
import initBlob from '~/pages/projects/init_blob';
import redirectToCorrectPage from '~/blame/blame_redirect';
redirectToCorrectPage();
initBlob();

View File

@ -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>

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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!

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.',

View File

@ -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
end
memoize_method :license
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)

View File

@ -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)

View File

@ -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?

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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 |

View File

@ -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",

View File

@ -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 -->

View File

@ -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.

View File

@ -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:

View File

@ -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 -->

View File

@ -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'] }

View File

@ -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

View File

@ -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

View File

@ -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
return true unless validate
schema_validation_passed = schema_validator.valid?
schema_validator.errors.each { |error| report.add_error('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) }
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.warnings.each { |warning| report.add_warning('Schema', warning) }
true
end
schema_validation_passed
end
def schema_validator

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 ""

View File

@ -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

View File

@ -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

View File

@ -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"
}
}

View File

@ -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"
}

View File

@ -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,9 +509,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.2"
}
}

View File

@ -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"
}
}

View File

@ -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"
}
}

View File

@ -18,6 +18,9 @@
"file": "aws-key.py",
"dependency": {
"package": {}
},
"commit": {
"sha": "e9c3a56590d5bed4155c0d128f1552d52fdcc7ae"
}
},
"identifiers": [

View File

@ -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.',
});
});
});

View File

@ -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 = {

View File

@ -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, {

View File

@ -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();

View File

@ -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

View File

@ -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);
});
});

View File

@ -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', () => {

View File

@ -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 () => {

View File

@ -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;

View File

@ -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(() => {

View File

@ -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: '/' });

View File

@ -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}`);

View File

@ -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 } = {}) => {

View File

@ -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,
);
});

View File

@ -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),

View File

@ -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);
});

View File

@ -34,7 +34,7 @@ describe('ConfidentialityFilter', () => {
wrapper = null;
});
const findRadioFilter = () => wrapper.find(RadioFilter);
const findRadioFilter = () => wrapper.findComponent(RadioFilter);
describe('template', () => {
beforeEach(() => {

View File

@ -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());

View File

@ -34,7 +34,7 @@ describe('StatusFilter', () => {
wrapper = null;
});
const findRadioFilter = () => wrapper.find(RadioFilter);
const findRadioFilter = () => wrapper.findComponent(RadioFilter);
describe('template', () => {
beforeEach(() => {

View File

@ -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());

View File

@ -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(() => {

View File

@ -53,7 +53,7 @@ describe('GroupFilter', () => {
wrapper.destroy();
});
const findSearchableDropdown = () => wrapper.find(SearchableDropdown);
const findSearchableDropdown = () => wrapper.findComponent(SearchableDropdown);
describe('template', () => {
beforeEach(() => {

View File

@ -53,7 +53,7 @@ describe('ProjectFilter', () => {
wrapper.destroy();
});
const findSearchableDropdown = () => wrapper.find(SearchableDropdown);
const findSearchableDropdown = () => wrapper.findComponent(SearchableDropdown);
describe('template', () => {
beforeEach(() => {

View File

@ -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);

View File

@ -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({

View File

@ -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`);
});

View File

@ -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);

View File

@ -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', () => {

View File

@ -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');

View File

@ -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(),

View File

@ -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');

View File

@ -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,

View File

@ -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', () => {

View File

@ -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);
});
});

View File

@ -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 () => {

View File

@ -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, {

View File

@ -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) => {

View File

@ -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', () => {

View File

@ -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]]);

View File

@ -17,7 +17,7 @@ describe('IssuableAssignees', () => {
},
});
};
const findUncollapsedAssigneeList = () => wrapper.find(UncollapsedAssigneeList);
const findUncollapsedAssigneeList = () => wrapper.findComponent(UncollapsedAssigneeList);
const findEmptyAssignee = () => wrapper.find('[data-testid="none"]');
afterEach(() => {

View File

@ -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');

View File

@ -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 () => {

View File

@ -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', () => {

View File

@ -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');

View File

@ -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);
});
});
});

View File

@ -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,
);
});

View File

@ -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);
});
});
});

View File

@ -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', () => {

View File

@ -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